txt_roll: Use git_roll_util rolls

Use fuchsia/git_roll_util Roll objects instead of pigweed/roll_util Roll
objects.

Bug: b/359925419
Change-Id: Icebf1d4e18174eeae3557fa2199c48b6377cb6c1
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/234512
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Commit-Queue: Rob Mohr <mohrr@google.com>
diff --git a/recipe_modules/txt_roll/__init__.py b/recipe_modules/txt_roll/__init__.py
index 1c57574..8ad2944 100644
--- a/recipe_modules/txt_roll/__init__.py
+++ b/recipe_modules/txt_roll/__init__.py
@@ -18,9 +18,6 @@
 
 DEPS = [
     'fuchsia/git_roll_util',
-    'pigweed/checkout',
-    'pigweed/roll_util',
     'recipe_engine/file',
-    'recipe_engine/path',
     'recipe_engine/step',
 ]
diff --git a/recipe_modules/txt_roll/api.py b/recipe_modules/txt_roll/api.py
index 12dfb24..24c9bbf 100644
--- a/recipe_modules/txt_roll/api.py
+++ b/recipe_modules/txt_roll/api.py
@@ -28,8 +28,8 @@
 if TYPE_CHECKING:  # pragma: no cover
     from typing import Generator
     from PB.recipe_modules.pigweed.txt_roll.txt_entry import TxtEntry
+    from RECIPE_MODULES.fuchsia.git_roll_util import api as git_roll_util_api
     from RECIPE_MODULES.pigweed.checkout import api as checkout_api
-    from RECIPE_MODULES.pigweed.roll_util import api as roll_util_api
 
 
 class TxtRollApi(recipe_api.RecipeApi):
@@ -38,7 +38,7 @@
         self,
         checkout: checkout_api.CheckoutContext,
         txt_entry: TxtEntry,
-    ) -> list[roll_util_api.Roll]:
+    ) -> list[git_roll_util_api.Roll]:
         branch = txt_entry.branch or 'main'
 
         new_revision = self.m.git_roll_util.resolve_new_revision(
@@ -47,24 +47,6 @@
             checkout.remotes_equivalent,
         )
 
-        project_dir = self.m.path.start_dir / 'project'
-
-        project_checkout = self.m.checkout(
-            CheckoutOptions(
-                remote=txt_entry.remote,
-                branch=branch,
-                use_trigger=True,
-            ),
-            root=project_dir,
-        )
-
-        # In case new_revision is a branch name we need to retrieve the hash it
-        # resolves to.
-        if not re.search(r'^[0-9a-f]{40}$', new_revision):
-            new_revision = self.m.checkout.get_revision(
-                project_dir, 'get new revision', test_data='2' * 40
-            )
-
         full_txt_path = checkout.root / txt_entry.path
 
         old_revision = self.m.file.read_text(
@@ -73,33 +55,22 @@
             test_data='1' * 40,
         ).strip()
 
+        with self.m.step.nest('txt_path') as pres:
+            pres.step_summary_text = repr(txt_entry.path)
+
+        try:
+            roll = self.m.git_roll_util.get_roll(
+                repo_url=txt_entry.remote,
+                repo_short_name=txt_entry.path,
+                old_rev=old_revision,
+                new_rev=new_revision,
+            )
+
+        except self.m.git_roll_util.BackwardsRollError:
+            return []
+
         self.m.file.write_text(
             'write new revision', full_txt_path, f'{new_revision}\n'
         )
 
-        direction = self.m.roll_util.get_roll_direction(
-            project_dir, old_revision, new_revision
-        )
-
-        # If the primary roll is not necessary or is backwards we can exit
-        # immediately.
-        if not self.m.roll_util.can_roll(direction):
-            self.m.roll_util.skip_roll_step(
-                txt_entry.remote,
-                old_revision,
-                new_revision,
-            )
-            return []
-
-        with self.m.step.nest('txt_path') as pres:
-            pres.step_summary_text = repr(txt_entry.path)
-
-        return [
-            self.m.roll_util.create_roll(
-                project_name=txt_entry.path,
-                old_revision=old_revision,
-                new_revision=new_revision,
-                proj_dir=project_dir,
-                direction=direction,
-            ),
-        ]
+        return [roll]
diff --git a/recipe_modules/txt_roll/tests/full.expected/backwards.json b/recipe_modules/txt_roll/tests/full.expected/backwards.json
index fb33211..a0803de 100644
--- a/recipe_modules/txt_roll/tests/full.expected/backwards.json
+++ b/recipe_modules/txt_roll/tests/full.expected/backwards.json
@@ -15,964 +15,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "checkout foo",
-    "~followup_annotations": [
-      "@@@STEP_LINK@applied foo:1234 (.)@https://foo-review.googlesource.com/c/1234@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.options",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@remote: \"https://foo.googlesource.com/foo\"\nbranch: \"main\"\nuse_trigger: true\n@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.options with defaults",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@remote: \"https://foo.googlesource.com/foo\"\nbranch: \"main\"\nmanifest_file: \"default.xml\"\nrepo_init_timeout_sec: 20\nrepo_sync_timeout_sec: 120\nnumber_of_attempts: 3\nsubmodule_timeout_sec: 600\nuse_trigger: true\n@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "ls-remote",
-      "--heads",
-      "https://foo.googlesource.com/foo",
-      "main"
-    ],
-    "name": "checkout foo.change data.git ls-remote",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_LOG_LINE@stdout@h3ll0\trefs/heads/main@@@",
-      "@@@STEP_LOG_END@stdout@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.ensure infra/tools/luci/gerrit/${platform}",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.ensure infra/tools/luci/gerrit/${platform}.get packages",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "RECIPE_MODULE[fuchsia::gerrit]/resources/cipd.ensure",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "name": "checkout foo.change data.ensure infra/tools/luci/gerrit/${platform}.get packages.read ensure file",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@",
-      "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gerrit/${platform} version:pinned-version@@@",
-      "@@@STEP_LOG_END@cipd.ensure@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0o777",
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07"
-    ],
-    "infra_step": true,
-    "name": "checkout foo.change data.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure package directory",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "cipd",
-      "ensure",
-      "-root",
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07",
-      "-ensure-file",
-      "infra/tools/luci/gerrit/${platform} version:pinned-version",
-      "-max-threads",
-      "0",
-      "-json-output",
-      "/path/to/tmp/json"
-    ],
-    "infra_step": true,
-    "name": "checkout foo.change data.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure_installed",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:pinned-v\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    ]@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-query",
-      "-host",
-      "https://foo-review.googlesource.com",
-      "-input",
-      "{\"params\": {\"q\": \"commit:h3ll0\"}}",
-      "-output",
-      "/path/to/tmp/json"
-    ],
-    "infra_step": true,
-    "name": "checkout foo.change data.number",
-    "timeout": 30,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_LOG_LINE@json.output@[@@@",
-      "@@@STEP_LOG_LINE@json.output@  {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"_number\": \"1234\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"project\": \"pigweed\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@]@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:h3ll0\"@@@",
-      "@@@STEP_LOG_LINE@json.input@  }@@@",
-      "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.changes",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.changes.foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@",
-      "@@@STEP_SUMMARY_TEXT@Change(number=1234, remote='https://foo.googlesource.com/foo', ref='h3ll0', rebase=False, project=None, branch='main', gerrit_name='foo', submitted=True, patchset=None, path=None, base=None, base_type=None, is_merge=False, commit_message='', topic=None, current_revision=None)@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.not matching branch names",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.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 foo.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 foo.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/foo.googlesource.com-foo"
-    ],
-    "infra_step": true,
-    "name": "checkout foo.cache.makedirs",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "init"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "name": "checkout foo.cache.git init",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "remote.origin.url",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "name": "checkout foo.cache.remote set-url",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "fetch.uriprotocols",
-      "https"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "name": "checkout foo.cache.set fetch.uriprotocols",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache.timeout 10s",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--prune",
-      "--tags",
-      "--jobs",
-      "4",
-      "origin",
-      "--no-recurse-submodules"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "name": "checkout foo.cache.git fetch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "name": "checkout foo.cache.git merge",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache.timeout 10s (2)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--recursive",
-      "--force",
-      "--jobs",
-      "4"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "name": "checkout foo.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 foo.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/foo.googlesource.com-foo",
-      "[START_DIR]/project"
-    ],
-    "infra_step": true,
-    "name": "checkout foo.copy from cache",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.git checkout",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.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]/project"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "deadline": {
-        "grace_period": 30.0,
-        "soft_deadline": 1337000019.0
-      }
-    },
-    "name": "checkout foo.git checkout.makedirs",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "init"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.git init",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "add",
-      "origin",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.git remote",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "core.longpaths",
-      "true"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.set core.longpaths",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "fetch.uriprotocols",
-      "https"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.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]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.git fetch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-f",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.git checkout",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-d",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.git checkout.git clean",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LINK@gerrit@https://foo-review.googlesource.com/c/1234@@@",
-      "@@@STEP_LINK@gitiles@https://foo.googlesource.com/foo/+/h3ll0@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--jobs",
-      "4",
-      "https://foo.googlesource.com/foo",
-      "h3ll0",
-      "--no-recurse-submodules"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.apply foo:1234.git fetch patch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "--force",
-      "-b",
-      "working",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.apply foo:1234.git checkout patch",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "add",
-      "https___foo_googlesource_com_foo",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.apply foo:1234.git remote add",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s (2)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--jobs",
-      "4",
-      "https___foo_googlesource_com_foo",
-      "refs/heads/main"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.apply foo:1234.git fetch branch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "branch",
-      "--set-upstream-to=https___foo_googlesource_com_foo/main"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.apply foo:1234.git set upstream",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.apply foo:1234.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--oneline",
-      "-n",
-      "10"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.apply foo:1234.post-rebase log",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s (3)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "--detach"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.apply foo:1234.detach",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--init",
-      "--recursive",
-      "--jobs",
-      "4"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.apply foo:1234.git submodule update",
-    "timeout": 600,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.apply foo:1234.reattach",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python3",
-      "RECIPE_MODULE[pigweed::checkout]/resources/submodule_status.py",
-      "[START_DIR]/project",
-      "/path/to/tmp/json",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.submodule status",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@{}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.matching foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@no matching submodules@@@",
-      "@@@STEP_LINK@gerrit@https://foo-review.googlesource.com/c/1234@@@",
-      "@@@STEP_LINK@gitiles@https://foo.googlesource.com/foo/+/h3ll0@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[{\"applied\": true, \"base\": \"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\", \"base_type\": \"submitted_commit_hash\", \"branch\": \"main\", \"commit_message\": \"\", \"current_revision\": null, \"gerrit_name\": \"foo\", \"is_merge\": false, \"number\": 1234, \"patchset\": null, \"path\": \".\", \"project\": null, \"rebase\": false, \"ref\": \"h3ll0\", \"remote\": \"https://foo.googlesource.com/foo\", \"submitted\": true, \"topic\": null}]",
-      "[CLEANUP]/tmp_tmp_1"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "name": "checkout foo.write changes.json",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@tmp_tmp_1@[{\"applied\": true, \"base\": \"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\", \"base_type\": \"submitted_commit_hash\", \"branch\": \"main\", \"commit_message\": \"\", \"current_revision\": null, \"gerrit_name\": \"foo\", \"is_merge\": false, \"number\": 1234, \"patchset\": null, \"path\": \".\", \"project\": null, \"rebase\": false, \"ref\": \"h3ll0\", \"remote\": \"https://foo.googlesource.com/foo\", \"submitted\": true, \"topic\": null}]@@@",
-      "@@@STEP_LOG_END@tmp_tmp_1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.git log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--oneline",
-      "-n",
-      "10"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.git log.[START_DIR]/project",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.base",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_type@\"submitted_commit_hash\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-f",
-      "-d"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.git clean",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "status"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.git status",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.status",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@applied [Change(number=1234, remote='https://foo.googlesource.com/foo', ref='h3ll0', rebase=False, project=None, branch='main', gerrit_name='foo', submitted=True, patchset=None, path='.', base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash', is_merge=False, commit_message='', topic=None, current_revision=None)]\nnot applied []@@@"
-    ]
-  },
-  {
-    "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 foo.mkdir",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "status",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "checkout foo.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 foo.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]/project",
-    "name": "checkout foo.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 foo.write git log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_END@git.log@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--max-count=1",
-      "--pretty=format:%H"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "get new revision",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@2222222222222222222222222222222222222222@@@"
-    ]
-  },
-  {
     "cmd": [
       "vpython3",
       "-u",
@@ -991,6 +33,24 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "txt_path",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@'foo.txt'@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "ensure infra/tools/luci/gitiles/${platform}"
+  },
+  {
+    "cmd": [],
+    "name": "ensure infra/tools/luci/gitiles/${platform}.get packages",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -998,67 +58,97 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "2222222222222222222222222222222222222222\n",
-      "[START_DIR]/checkout/foo.txt"
+      "RECIPE_MODULE[fuchsia::gitiles]/resources/cipd.ensure",
+      "/path/to/tmp/"
     ],
     "infra_step": true,
-    "name": "write new revision",
+    "name": "ensure infra/tools/luci/gitiles/${platform}.get packages.read ensure file",
     "~followup_annotations": [
-      "@@@STEP_LOG_LINE@foo.txt@2222222222222222222222222222222222222222@@@",
-      "@@@STEP_LOG_END@foo.txt@@@"
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gitiles/${platform} version:pinned-version@@@",
+      "@@@STEP_LOG_END@cipd.ensure@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "get roll direction",
-    "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@backward@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge-base",
-      "--is-ancestor",
-      "1111111111111111111111111111111111111111",
-      "2222222222222222222222222222222222222222"
-    ],
-    "cwd": "[START_DIR]/project",
-    "name": "get roll direction.is forward",
-    "timeout": 600.0,
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
   },
   {
     "cmd": [
-      "git",
-      "merge-base",
-      "--is-ancestor",
-      "2222222222222222222222222222222222222222",
-      "1111111111111111111111111111111111111111"
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07"
     ],
-    "cwd": "[START_DIR]/project",
-    "name": "get roll direction.is backward",
-    "timeout": 600.0,
+    "infra_step": true,
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles.ensure package directory",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07",
+      "-ensure-file",
+      "infra/tools/luci/gitiles/${platform} version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:pinned-v\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gitiles/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gitiles",
+      "log",
+      "-json-output",
+      "/path/to/tmp/json",
+      "https://foo.googlesource.com/foo",
+      "1111111111111111111111111111111111111111..h3ll0"
+    ],
+    "infra_step": true,
+    "name": "log foo.txt",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "cancelling roll",
+    "name": "detected backwards roll",
     "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@not updating from 1111111 to 2222222 because 1111111 is newer than 2222222@@@",
-      "@@@STEP_LINK@1111111111111111111111111111111111111111@https://foo.googlesource.com/foo/+/1111111111111111111111111111111111111111@@@",
-      "@@@STEP_LINK@2222222222222222222222222222222222222222@https://foo.googlesource.com/foo/+/2222222222222222222222222222222222222222@@@"
+      "@@@STEP_TEXT@Detected backwards roll: expected 1111111 to precede h3ll0 in git history@@@"
     ]
   },
   {
-    "cmd": [],
-    "name": "nothing to roll, exiting"
-  },
-  {
     "name": "$result"
   }
 ]
\ No newline at end of file
diff --git a/recipe_modules/txt_roll/tests/full.expected/success.json b/recipe_modules/txt_roll/tests/full.expected/success.json
index a6d1b98..c8398f2 100644
--- a/recipe_modules/txt_roll/tests/full.expected/success.json
+++ b/recipe_modules/txt_roll/tests/full.expected/success.json
@@ -1,56 +1,295 @@
 [
   {
-    "cmd": [],
-    "name": "checkout foo",
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/checkout/foo.txt",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "read old revision",
     "~followup_annotations": [
-      "@@@STEP_LINK@applied foo:1234 (.)@https://foo-review.googlesource.com/c/1234@@@"
+      "@@@STEP_LOG_LINE@foo.txt@1111111111111111111111111111111111111111@@@",
+      "@@@STEP_LOG_END@foo.txt@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "checkout foo.options",
+    "name": "txt_path",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@remote: \"https://foo.googlesource.com/foo\"\nbranch: \"main\"\nuse_trigger: true\n@@@"
+      "@@@STEP_SUMMARY_TEXT@'foo.txt'@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "checkout foo.options with defaults",
+    "name": "ensure infra/tools/luci/gitiles/${platform}"
+  },
+  {
+    "cmd": [],
+    "name": "ensure infra/tools/luci/gitiles/${platform}.get packages",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@remote: \"https://foo.googlesource.com/foo\"\nbranch: \"main\"\nmanifest_file: \"default.xml\"\nrepo_init_timeout_sec: 20\nrepo_sync_timeout_sec: 120\nnumber_of_attempts: 3\nsubmodule_timeout_sec: 600\nuse_trigger: true\n@@@"
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gitiles]/resources/cipd.ensure",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "ensure infra/tools/luci/gitiles/${platform}.get packages.read ensure file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gitiles/${platform} version:pinned-version@@@",
+      "@@@STEP_LOG_END@cipd.ensure@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "checkout foo.change data",
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07",
+      "-ensure-file",
+      "infra/tools/luci/gitiles/${platform} version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:pinned-v\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gitiles/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gitiles",
+      "log",
+      "-json-output",
+      "/path/to/tmp/json",
+      "https://foo.googlesource.com/foo",
+      "1111111111111111111111111111111111111111..2d72510e447ab60a9728aeea2362d8be2cbd7789"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "log foo.txt",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"author\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_0.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"committer\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_0.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"id\": \"3e30158f2a7caccb7a9f6632a60011e7a44e1e5c\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"message\": \"fake A msg 0\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"parents\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"83a7614b3b60951511be50db1b9561daff4bb447\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree\": \"1b6412b24ec3add84836c8fdd1af5ac8e35b61d9\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree_diff\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_id\": \"8bea05ad53680fce6937543f0d98cd48e295b8ff\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_mode\": 33188,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_path\": \"a.py\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_id\": \"0000000000000000000000000000000000000000\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_mode\": 0,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"type\": \"add\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  },@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"author\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_1.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"committer\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_1.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"id\": \"3380b83c11e029b7291c83c44e7b1ce09d465fd1\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"message\": \"fake A msg 1\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"parents\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"8675a52c73c701cb0b2c48f5ed4a9058c624e6cd\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree\": \"a1b1e6aa501915989b45a95e1224ec2a88655eb3\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree_diff\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_id\": \"06bc4c79002f278528aaddae4e056a11f58c19ad\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_mode\": 33188,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_path\": \"b.py\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_id\": \"0000000000000000000000000000000000000000\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_mode\": 0,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"type\": \"add\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  },@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"author\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_2.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"committer\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_2.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"id\": \"363caa907186de786cb5292cd1ab7245da954815\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"message\": \"fake A msg 2\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"parents\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"255c6325c4c654e17e6b35142e3912c86f1718f2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree\": \"e84d4ad259e69da73d2b842e2b9709f08e8b22bd\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree_diff\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_id\": \"d7f478bf423219f2f47c1a6ed344fc597a8bf18f\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_mode\": 33188,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_path\": \"c.py\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_id\": \"0000000000000000000000000000000000000000\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_mode\": 0,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"type\": \"add\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "get gerrit change numbers"
+  },
+  {
+    "cmd": [],
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit",
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.get packages",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@2@@@"
     ]
   },
   {
-    "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.get packages",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@"
-    ]
-  },
-  {
     "cmd": [
       "vpython3",
       "-u",
@@ -74,18 +313,18 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.get packages.read ensure file",
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.get packages.read ensure file",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@STEP_NEST_LEVEL@3@@@",
       "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gerrit/${platform} version:pinned-version@@@",
       "@@@STEP_LOG_END@cipd.ensure@@@"
     ]
   },
   {
     "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit",
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@"
+      "@@@STEP_NEST_LEVEL@2@@@"
     ]
   },
   {
@@ -113,9 +352,9 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure package directory",
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure package directory",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@5@@@"
+      "@@@STEP_NEST_LEVEL@3@@@"
     ]
   },
   {
@@ -144,9 +383,9 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure_installed",
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure_installed",
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@5@@@",
+      "@@@STEP_NEST_LEVEL@3@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
@@ -163,11 +402,11 @@
   {
     "cmd": [
       "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-query",
+      "change-detail",
       "-host",
       "https://foo-review.googlesource.com",
       "-input",
-      "{\"params\": {\"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"}}",
+      "{\"change_id\": \"3e30158f2a7caccb7a9f6632a60011e7a44e1e5c\"}",
       "-output",
       "/path/to/tmp/json"
     ],
@@ -184,67 +423,31 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "checkout foo.change data.process gitiles commit.number",
-    "timeout": 30,
+    "name": "get gerrit change numbers.change details for 3e30158",
+    "timeout": 600,
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@",
-      "@@@STEP_LOG_LINE@json.output@[@@@",
-      "@@@STEP_LOG_LINE@json.output@  {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"_number\": \"1234\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"project\": \"pigweed\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 12345@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
       "@@@STEP_LOG_END@json.output@@@",
       "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"@@@",
-      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"3e30158f2a7caccb7a9f6632a60011e7a44e1e5c\"@@@",
       "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.changes",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.changes.foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@",
-      "@@@STEP_SUMMARY_TEXT@Change(number=1234, remote='https://foo.googlesource.com/foo', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, project='pigweed', branch='main', gerrit_name='foo', submitted=True, patchset=None, path=None, base=None, base_type=None, is_merge=False, commit_message='', topic=None, current_revision='2d72510e447ab60a9728aeea2362d8be2cbd7789')@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.not matching branch names",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@miss@@@"
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://foo-review.googlesource.com/q/3e30158f2a7caccb7a9f6632a60011e7a44e1e5c@@@"
     ]
   },
   {
     "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0o777",
-      "[CACHE]/git"
+      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
+      "change-detail",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3380b83c11e029b7291c83c44e7b1ce09d465fd1\"}",
+      "-output",
+      "/path/to/tmp/json"
     ],
     "infra_step": true,
     "luci_context": {
@@ -259,626 +462,32 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "checkout foo.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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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/foo.googlesource.com-foo"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.makedirs",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "init"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git init",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "remote.origin.url",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.remote set-url",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "fetch.uriprotocols",
-      "https"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.set fetch.uriprotocols",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache.timeout 10s",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--prune",
-      "--tags",
-      "--jobs",
-      "4",
-      "origin",
-      "--no-recurse-submodules"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git fetch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git merge",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache.timeout 10s (2)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--recursive",
-      "--force",
-      "--jobs",
-      "4"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git submodule update",
+    "name": "get gerrit change numbers.change details for 3380b83",
     "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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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/foo.googlesource.com-foo",
-      "[START_DIR]/project"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.copy from cache",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.git checkout",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.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]/project"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "deadline": {
-        "grace_period": 30.0,
-        "soft_deadline": 1337000019.0
-      },
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.makedirs",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "init"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git init",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "add",
-      "origin",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git remote",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "core.longpaths",
-      "true"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.set core.longpaths",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "fetch.uriprotocols",
-      "https"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git fetch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-f",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git checkout",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-d",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git clean",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234",
-    "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LINK@gerrit@https://foo-review.googlesource.com/c/1234@@@",
-      "@@@STEP_LINK@gitiles@https://foo.googlesource.com/foo/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 12345@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"3380b83c11e029b7291c83c44e7b1ce09d465fd1\"@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://foo-review.googlesource.com/q/3380b83c11e029b7291c83c44e7b1ce09d465fd1@@@"
     ]
   },
   {
     "cmd": [
-      "git",
-      "fetch",
-      "--jobs",
-      "4",
-      "https://foo.googlesource.com/foo",
-      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
-      "--no-recurse-submodules"
+      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
+      "change-detail",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"363caa907186de786cb5292cd1ab7245da954815\"}",
+      "-output",
+      "/path/to/tmp/json"
     ],
-    "cwd": "[START_DIR]/project",
     "infra_step": true,
     "luci_context": {
       "realm": {
@@ -892,614 +501,19 @@
         "hostname": "rdbhost"
       }
     },
-    "name": "checkout foo.apply foo:1234.git fetch patch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "--force",
-      "-b",
-      "working",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git checkout patch",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "add",
-      "https___foo_googlesource_com_foo",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git remote add",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s (2)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--jobs",
-      "4",
-      "https___foo_googlesource_com_foo",
-      "refs/heads/main"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git fetch branch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "branch",
-      "--set-upstream-to=https___foo_googlesource_com_foo/main"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git set upstream",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--oneline",
-      "-n",
-      "10"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.post-rebase log",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s (3)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "--detach"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.detach",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--init",
-      "--recursive",
-      "--jobs",
-      "4"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git submodule update",
+    "name": "get gerrit change numbers.change details for 363caa9",
     "timeout": 600,
     "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.reattach",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python3",
-      "RECIPE_MODULE[pigweed::checkout]/resources/submodule_status.py",
-      "[START_DIR]/project",
-      "/path/to/tmp/json",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.submodule status",
-    "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@{}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.matching foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@no matching submodules@@@",
-      "@@@STEP_LINK@gerrit@https://foo-review.googlesource.com/c/1234@@@",
-      "@@@STEP_LINK@gitiles@https://foo.googlesource.com/foo/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[{\"applied\": true, \"base\": \"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\", \"base_type\": \"submitted_commit_hash\", \"branch\": \"main\", \"commit_message\": \"\", \"current_revision\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"gerrit_name\": \"foo\", \"is_merge\": false, \"number\": 1234, \"patchset\": null, \"path\": \".\", \"project\": \"pigweed\", \"rebase\": false, \"ref\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"remote\": \"https://foo.googlesource.com/foo\", \"submitted\": true, \"topic\": null}]",
-      "[CLEANUP]/tmp_tmp_1"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.write changes.json",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@tmp_tmp_1@[{\"applied\": true, \"base\": \"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\", \"base_type\": \"submitted_commit_hash\", \"branch\": \"main\", \"commit_message\": \"\", \"current_revision\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"gerrit_name\": \"foo\", \"is_merge\": false, \"number\": 1234, \"patchset\": null, \"path\": \".\", \"project\": \"pigweed\", \"rebase\": false, \"ref\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"remote\": \"https://foo.googlesource.com/foo\", \"submitted\": true, \"topic\": null}]@@@",
-      "@@@STEP_LOG_END@tmp_tmp_1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.git log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--oneline",
-      "-n",
-      "10"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git log.[START_DIR]/project",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.base",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_type@\"submitted_commit_hash\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-f",
-      "-d"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git clean",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "status"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git status",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.status",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@applied [Change(number=1234, remote='https://foo.googlesource.com/foo', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, project='pigweed', branch='main', gerrit_name='foo', submitted=True, patchset=None, path='.', base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash', is_merge=False, commit_message='', topic=None, current_revision='2d72510e447ab60a9728aeea2362d8be2cbd7789')]\nnot applied []@@@"
-    ]
-  },
-  {
-    "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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.mkdir",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "status",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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]/checkout/foo.txt",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "read old revision",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@foo.txt@1111111111111111111111111111111111111111@@@",
-      "@@@STEP_LOG_END@foo.txt@@@"
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 12345@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"363caa907186de786cb5292cd1ab7245da954815\"@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://foo-review.googlesource.com/q/363caa907186de786cb5292cd1ab7245da954815@@@"
     ]
   },
   {
@@ -1534,257 +548,9 @@
   },
   {
     "cmd": [],
-    "name": "get roll direction",
+    "name": "commit message",
     "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@forward@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge-base",
-      "--is-ancestor",
-      "1111111111111111111111111111111111111111",
-      "2d72510e447ab60a9728aeea2362d8be2cbd7789"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "get roll direction.is forward",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge-base",
-      "--is-ancestor",
-      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
-      "1111111111111111111111111111111111111111"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "get roll direction.is backward",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "txt_path",
-    "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@'foo.txt'@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "remote"
-  },
-  {
-    "cmd": [
-      "git",
-      "remote"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "remote.name",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "get-url",
-      "origin"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "remote.url",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "foo.txt"
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--pretty=format:%H\n%an\n%ae\n%B",
-      "-z",
-      "1111111111111111111111111111111111111111..2d72510e447ab60a9728aeea2362d8be2cbd7789"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "foo.txt.git log",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-query",
-      "-host",
-      "https://pigweed-review.googlesource.com",
-      "-input",
-      "{\"params\": {\"q\": \"commit:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}",
-      "-output",
-      "/path/to/tmp/json"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "foo.txt.get change-id",
-    "timeout": 600,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@[@@@",
-      "@@@STEP_LOG_LINE@json.output@  {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"_number\": 12345@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@]@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"@@@",
-      "@@@STEP_LOG_LINE@json.input@  }@@@",
-      "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-detail",
-      "-host",
-      "https://pigweed-review.googlesource.com",
-      "-input",
-      "{\"change_id\": \"12345\"}",
-      "-output",
-      "/path/to/tmp/json"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "foo.txt.get 12345",
-    "timeout": 600,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"owner\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"email\": \"author@example.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"name\": \"author\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  },@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"reviewers\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"REVIEWER\": [@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"email\": \"reviewer@example.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"name\": \"reviewer\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      },@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"email\": \"nobody@google.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"name\": \"nobody\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      },@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"email\": \"robot@gserviceaccount.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"name\": \"robot\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    ]@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"12345\"@@@",
-      "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@",
-      "@@@STEP_LINK@gerrit link@https://pigweed-review.googlesource.com/q/12345@@@"
+      "@@@STEP_SUMMARY_TEXT@roll: foo.txt 1111111..2d72510 (3 commits)\n\n3e30158:https://foo-review.googlesource.com/c/foo/+/12345 fake A msg 0\n3380b83:https://foo-review.googlesource.com/c/foo/+/12345 fake A msg 1\n363caa9:https://foo-review.googlesource.com/c/foo/+/12345 fake A msg 2\n\n\nRolled-Repo: https://foo.googlesource.com/foo\nRolled-Commits: 11111111111111..2d72510e447ab6@@@"
     ]
   },
   {
diff --git a/recipe_modules/txt_roll/tests/full.proto b/recipe_modules/txt_roll/tests/full.proto
index e669504..32ea70a 100644
--- a/recipe_modules/txt_roll/tests/full.proto
+++ b/recipe_modules/txt_roll/tests/full.proto
@@ -21,6 +21,8 @@
 import "recipe_modules/fuchsia/auto_roller/options.proto";
 
 message InputProperties {
-  // Submodules to update.
+  // Text entries to update.
   repeated recipe_modules.pigweed.txt_roll.TxtEntry txt_entries = 1;
+
+  string commit_divider = 2;
 }
diff --git a/recipe_modules/txt_roll/tests/full.py b/recipe_modules/txt_roll/tests/full.py
index def037a..ffc0b92 100644
--- a/recipe_modules/txt_roll/tests/full.py
+++ b/recipe_modules/txt_roll/tests/full.py
@@ -17,8 +17,9 @@
 from PB.recipe_modules.pigweed.txt_roll.tests.full import InputProperties
 
 DEPS = [
+    "fuchsia/git_roll_util",
+    "fuchsia/gitiles",
     "pigweed/checkout",
-    "pigweed/roll_util",
     "pigweed/txt_roll",
     "recipe_engine/properties",
     "recipe_engine/step",
@@ -38,9 +39,13 @@
 
     rolls = api.txt_roll.update(checkout=checkout, txt_entry=txt_entry)
 
-    if not rolls:
-        with api.step.nest('nothing to roll, exiting'):
-            return
+    if rolls:
+        pres = api.step.empty('commit message').presentation
+        pres.step_summary_text = api.git_roll_util.format_commit_message(
+            *rolls,
+            roll_prefix="roll:",
+            send_comment=True,
+        )
 
 
 def GenTests(api) -> Generator[recipe_test_api.TestData, None, None]:
@@ -58,24 +63,18 @@
         props.txt_entries.extend(txt_entries)
         return api.properties(props)
 
-    def commit_data(name, **kwargs):
-        return api.roll_util.commit_data(
-            name,
-            api.roll_util.commit('a' * 40, 'foo\nbar\n\nChange-Id: I1111'),
-            **kwargs,
-        )
-
     yield api.test(
         'success',
-        properties(api.txt_roll.entry(path='foo.txt', remote=_url("foo"))),
-        api.roll_util.properties(commit_divider='--divider--'),
+        properties(
+            api.txt_roll.entry(path='foo.txt', remote=_url("foo")),
+            commit_divider='--divider--',
+        ),
         trigger('foo'),
-        api.roll_util.forward_roll(),
-        commit_data('foo.txt', prefix=''),
+        api.gitiles.log("log foo.txt", "A"),
     )
 
     yield api.test(
         'backwards',
         properties(api.txt_roll.entry(path='foo.txt', remote=_url("foo"))),
-        api.roll_util.backward_roll(),
+        api.gitiles.log("log foo.txt", "A", n=0),
     )
diff --git a/recipes/static_checks.py b/recipes/static_checks.py
index 0f02985..1710753 100644
--- a/recipes/static_checks.py
+++ b/recipes/static_checks.py
@@ -34,7 +34,6 @@
     'recipe_engine/buildbucket',
     'recipe_engine/cv',
     'recipe_engine/json',
-    'recipe_engine/path',
     'recipe_engine/properties',
     'recipe_engine/step',
 ]
diff --git a/recipes/txt_roller.expected/success.json b/recipes/txt_roller.expected/success.json
index df6d680..aadb81f 100644
--- a/recipes/txt_roller.expected/success.json
+++ b/recipes/txt_roller.expected/success.json
@@ -950,1449 +950,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "checkout foo",
-    "~followup_annotations": [
-      "@@@STEP_LINK@applied foo:1234 (.)@https://foo-review.googlesource.com/c/1234@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.options",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@remote: \"https://foo.googlesource.com/foo\"\nbranch: \"main\"\nuse_trigger: true\n@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.options with defaults",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@remote: \"https://foo.googlesource.com/foo\"\nbranch: \"main\"\nmanifest_file: \"default.xml\"\nrepo_init_timeout_sec: 20\nrepo_sync_timeout_sec: 120\nnumber_of_attempts: 3\nsubmodule_timeout_sec: 600\nuse_trigger: true\n@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.get packages",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "RECIPE_MODULE[fuchsia::gerrit]/resources/cipd.ensure",
-      "/path/to/tmp/"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.get packages.read ensure file",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@5@@@",
-      "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gerrit/${platform} version:pinned-version@@@",
-      "@@@STEP_LOG_END@cipd.ensure@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@4@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0o777",
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure package directory",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@5@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "cipd",
-      "ensure",
-      "-root",
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07",
-      "-ensure-file",
-      "infra/tools/luci/gerrit/${platform} version:pinned-version",
-      "-max-threads",
-      "0",
-      "-json-output",
-      "/path/to/tmp/json"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.change data.process gitiles commit.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure_installed",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@5@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:pinned-v\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    ]@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-query",
-      "-host",
-      "https://foo-review.googlesource.com",
-      "-input",
-      "{\"params\": {\"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"}}",
-      "-output",
-      "/path/to/tmp/json"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.change data.process gitiles commit.number",
-    "timeout": 30,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@",
-      "@@@STEP_LOG_LINE@json.output@[@@@",
-      "@@@STEP_LOG_LINE@json.output@  {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"_number\": \"1234\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"branch\": \"main\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"project\": \"pigweed\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@]@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"@@@",
-      "@@@STEP_LOG_LINE@json.input@  }@@@",
-      "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.changes",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.change data.changes.foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@3@@@",
-      "@@@STEP_SUMMARY_TEXT@Change(number=1234, remote='https://foo.googlesource.com/foo', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, project='pigweed', branch='main', gerrit_name='foo', submitted=True, patchset=None, path=None, base=None, base_type=None, is_merge=False, commit_message='', topic=None, current_revision='2d72510e447ab60a9728aeea2362d8be2cbd7789')@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.not matching branch names",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.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",
-      "copy",
-      "",
-      "[CACHE]/git/.GUARD_FILE"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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/foo.googlesource.com-foo"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.makedirs",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "init"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git init",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "remote.origin.url",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.remote set-url",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "fetch.uriprotocols",
-      "https"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.set fetch.uriprotocols",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache.timeout 10s",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--prune",
-      "--tags",
-      "--jobs",
-      "4",
-      "origin",
-      "--no-recurse-submodules"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git fetch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.cache.git merge",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.cache.timeout 10s (2)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--recursive",
-      "--force",
-      "--jobs",
-      "4"
-    ],
-    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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/foo.googlesource.com-foo",
-      "[START_DIR]/project"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.copy from cache",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.git checkout",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.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]/project"
-    ],
-    "infra_step": true,
-    "luci_context": {
-      "deadline": {
-        "grace_period": 30.0,
-        "soft_deadline": 1337000028.0
-      },
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.makedirs",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "init"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git init",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "add",
-      "origin",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git remote",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "core.longpaths",
-      "true"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.set core.longpaths",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "config",
-      "fetch.uriprotocols",
-      "https"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git fetch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-f",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git checkout",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-d",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git checkout.git clean",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LINK@gerrit@https://foo-review.googlesource.com/c/1234@@@",
-      "@@@STEP_LINK@gitiles@https://foo.googlesource.com/foo/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--jobs",
-      "4",
-      "https://foo.googlesource.com/foo",
-      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
-      "--no-recurse-submodules"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git fetch patch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "--force",
-      "-b",
-      "working",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git checkout patch",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "add",
-      "https___foo_googlesource_com_foo",
-      "https://foo.googlesource.com/foo"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git remote add",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s (2)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "--jobs",
-      "4",
-      "https___foo_googlesource_com_foo",
-      "refs/heads/main"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git fetch branch",
-    "timeout": 1200.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "branch",
-      "--set-upstream-to=https___foo_googlesource_com_foo/main"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git set upstream",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git rev-parse",
-    "timeout": 300.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--oneline",
-      "-n",
-      "10"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.post-rebase log",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.apply foo:1234.timeout 10s (3)",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "--detach"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.detach",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--init",
-      "--recursive",
-      "--jobs",
-      "4"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.git submodule update",
-    "timeout": 600,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.apply foo:1234.reattach",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python3",
-      "RECIPE_MODULE[pigweed::checkout]/resources/submodule_status.py",
-      "[START_DIR]/project",
-      "/path/to/tmp/json",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.submodule status",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@{}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.matching foo:1234",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@no matching submodules@@@",
-      "@@@STEP_LINK@gerrit@https://foo-review.googlesource.com/c/1234@@@",
-      "@@@STEP_LINK@gitiles@https://foo.googlesource.com/foo/+/2d72510e447ab60a9728aeea2362d8be2cbd7789@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copy",
-      "[{\"applied\": true, \"base\": \"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\", \"base_type\": \"submitted_commit_hash\", \"branch\": \"main\", \"commit_message\": \"\", \"current_revision\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"gerrit_name\": \"foo\", \"is_merge\": false, \"number\": 1234, \"patchset\": null, \"path\": \".\", \"project\": \"pigweed\", \"rebase\": false, \"ref\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"remote\": \"https://foo.googlesource.com/foo\", \"submitted\": true, \"topic\": null}]",
-      "[CLEANUP]/tmp_tmp_1"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.write changes.json",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@tmp_tmp_1@[{\"applied\": true, \"base\": \"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\", \"base_type\": \"submitted_commit_hash\", \"branch\": \"main\", \"commit_message\": \"\", \"current_revision\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"gerrit_name\": \"foo\", \"is_merge\": false, \"number\": 1234, \"patchset\": null, \"path\": \".\", \"project\": \"pigweed\", \"rebase\": false, \"ref\": \"2d72510e447ab60a9728aeea2362d8be2cbd7789\", \"remote\": \"https://foo.googlesource.com/foo\", \"submitted\": true, \"topic\": null}]@@@",
-      "@@@STEP_LOG_END@tmp_tmp_1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.git log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--oneline",
-      "-n",
-      "10"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git log.[START_DIR]/project",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.base",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_\"@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision_type@\"submitted_commit_hash\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-f",
-      "-d"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git clean",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "status"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.git status",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "checkout foo.status",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_SUMMARY_TEXT@applied [Change(number=1234, remote='https://foo.googlesource.com/foo', ref='2d72510e447ab60a9728aeea2362d8be2cbd7789', rebase=False, project='pigweed', branch='main', gerrit_name='foo', submitted=True, patchset=None, path='.', base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash', is_merge=False, commit_message='', topic=None, current_revision='2d72510e447ab60a9728aeea2362d8be2cbd7789')]\nnot applied []@@@"
-    ]
-  },
-  {
-    "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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.mkdir",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "status",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.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,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "checkout foo.write git log",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_END@git.log@@@"
-    ]
-  },
-  {
     "cmd": [
       "vpython3",
       "-u",
@@ -2423,6 +980,493 @@
     ]
   },
   {
+    "cmd": [],
+    "name": "txt_path",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@'foo.txt'@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "ensure infra/tools/luci/gitiles/${platform}"
+  },
+  {
+    "cmd": [],
+    "name": "ensure infra/tools/luci/gitiles/${platform}.get packages",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gitiles]/resources/cipd.ensure",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "ensure infra/tools/luci/gitiles/${platform}.get packages.read ensure file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gitiles/${platform} version:pinned-version@@@",
+      "@@@STEP_LOG_END@cipd.ensure@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0o777",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07",
+      "-ensure-file",
+      "infra/tools/luci/gitiles/${platform} version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "ensure infra/tools/luci/gitiles/${platform}.install infra/tools/luci/gitiles.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:pinned-v\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gitiles/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/infra/tools/luci/gitiles/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gitiles",
+      "log",
+      "-json-output",
+      "/path/to/tmp/json",
+      "https://foo.googlesource.com/foo",
+      "1111111111111111111111111111111111111111..2d72510e447ab60a9728aeea2362d8be2cbd7789"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "log foo.txt",
+    "timeout": 300.0,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@[@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"author\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_0.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"committer\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_0.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"id\": \"3e30158f2a7caccb7a9f6632a60011e7a44e1e5c\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"message\": \"fake A msg 0\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"parents\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"83a7614b3b60951511be50db1b9561daff4bb447\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree\": \"1b6412b24ec3add84836c8fdd1af5ac8e35b61d9\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree_diff\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_id\": \"8bea05ad53680fce6937543f0d98cd48e295b8ff\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_mode\": 33188,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_path\": \"a.py\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_id\": \"0000000000000000000000000000000000000000\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_mode\": 0,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"type\": \"add\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  },@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"author\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_1.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"committer\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_1.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"id\": \"3380b83c11e029b7291c83c44e7b1ce09d465fd1\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"message\": \"fake A msg 1\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"parents\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"8675a52c73c701cb0b2c48f5ed4a9058c624e6cd\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree\": \"a1b1e6aa501915989b45a95e1224ec2a88655eb3\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree_diff\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_id\": \"06bc4c79002f278528aaddae4e056a11f58c19ad\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_mode\": 33188,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_path\": \"b.py\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_id\": \"0000000000000000000000000000000000000000\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_mode\": 0,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"type\": \"add\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  },@@@",
+      "@@@STEP_LOG_LINE@json.output@  {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"author\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_2.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"committer\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"email\": \"fake_A@fake_2.email.com\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"name\": \"Fake A\",@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"time\": \"Mon Jan 01 00:00:00 2015\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    },@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"id\": \"363caa907186de786cb5292cd1ab7245da954815\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"message\": \"fake A msg 2\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"parents\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"255c6325c4c654e17e6b35142e3912c86f1718f2\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree\": \"e84d4ad259e69da73d2b842e2b9709f08e8b22bd\",@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"tree_diff\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_id\": \"d7f478bf423219f2f47c1a6ed344fc597a8bf18f\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_mode\": 33188,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"new_path\": \"c.py\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_id\": \"0000000000000000000000000000000000000000\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"old_mode\": 0,@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"type\": \"add\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@]@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "get gerrit change numbers"
+  },
+  {
+    "cmd": [],
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.get packages",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/cipd.ensure",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.get packages.read ensure file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@cipd.ensure@infra/tools/luci/gerrit/${platform} version:pinned-version@@@",
+      "@@@STEP_LOG_END@cipd.ensure@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit",
+    "~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]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07",
+      "-ensure-file",
+      "infra/tools/luci/gerrit/${platform} version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get gerrit change numbers.ensure infra/tools/luci/gerrit/${platform}.install infra/tools/luci/gerrit.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-version:pinned-v\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/luci/gerrit/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
+      "change-detail",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3e30158f2a7caccb7a9f6632a60011e7a44e1e5c\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get gerrit change numbers.change details for 3e30158",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 12345@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"3e30158f2a7caccb7a9f6632a60011e7a44e1e5c\"@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://foo-review.googlesource.com/q/3e30158f2a7caccb7a9f6632a60011e7a44e1e5c@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
+      "change-detail",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"3380b83c11e029b7291c83c44e7b1ce09d465fd1\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get gerrit change numbers.change details for 3380b83",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 12345@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"3380b83c11e029b7291c83c44e7b1ce09d465fd1\"@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://foo-review.googlesource.com/q/3380b83c11e029b7291c83c44e7b1ce09d465fd1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
+      "change-detail",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"363caa907186de786cb5292cd1ab7245da954815\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "get gerrit change numbers.change details for 363caa9",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"_number\": 12345@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"363caa907186de786cb5292cd1ab7245da954815\"@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://foo-review.googlesource.com/q/363caa907186de786cb5292cd1ab7245da954815@@@"
+    ]
+  },
+  {
     "cmd": [
       "vpython3",
       "-u",
@@ -2453,291 +1497,6 @@
     ]
   },
   {
-    "cmd": [],
-    "name": "get roll direction",
-    "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@forward@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge-base",
-      "--is-ancestor",
-      "1111111111111111111111111111111111111111",
-      "2d72510e447ab60a9728aeea2362d8be2cbd7789"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "get roll direction.is forward",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "merge-base",
-      "--is-ancestor",
-      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
-      "1111111111111111111111111111111111111111"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "get roll direction.is backward",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "txt_path",
-    "~followup_annotations": [
-      "@@@STEP_SUMMARY_TEXT@'foo.txt'@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "remote"
-  },
-  {
-    "cmd": [
-      "git",
-      "remote"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "remote.name",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "remote",
-      "get-url",
-      "origin"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "remote.url",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "foo.txt"
-  },
-  {
-    "cmd": [
-      "git",
-      "log",
-      "--pretty=format:%H\n%an\n%ae\n%B",
-      "-z",
-      "1111111111111111111111111111111111111111..2d72510e447ab60a9728aeea2362d8be2cbd7789"
-    ],
-    "cwd": "[START_DIR]/project",
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "foo.txt.git log",
-    "timeout": 600.0,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-query",
-      "-host",
-      "https://pigweed-review.googlesource.com",
-      "-input",
-      "{\"params\": {\"q\": \"commit:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}",
-      "-output",
-      "/path/to/tmp/json"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "foo.txt.get change-id",
-    "timeout": 600,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@[@@@",
-      "@@@STEP_LOG_LINE@json.output@  {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"_number\": 12345@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@]@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
-      "@@@STEP_LOG_LINE@json.input@    \"q\": \"commit:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"@@@",
-      "@@@STEP_LOG_LINE@json.input@  }@@@",
-      "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[START_DIR]/cipd_tool/infra/tools/luci/gerrit/0e548aa33f8113a45a5b3b62201e114e98e63d00f97296912380138f44597b07/gerrit",
-      "change-detail",
-      "-host",
-      "https://pigweed-review.googlesource.com",
-      "-input",
-      "{\"change_id\": \"12345\"}",
-      "-output",
-      "/path/to/tmp/json"
-    ],
-    "cwd": "[START_DIR]/project",
-    "infra_step": true,
-    "luci_context": {
-      "realm": {
-        "name": "project:ci"
-      },
-      "resultdb": {
-        "current_invocation": {
-          "name": "invocations/build:8945511751514863184",
-          "update_token": "token"
-        },
-        "hostname": "rdbhost"
-      }
-    },
-    "name": "foo.txt.get 12345",
-    "timeout": 600,
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"owner\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"email\": \"author@example.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"name\": \"author\"@@@",
-      "@@@STEP_LOG_LINE@json.output@  },@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"reviewers\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"REVIEWER\": [@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"email\": \"reviewer@example.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"name\": \"reviewer\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      },@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"email\": \"nobody@google.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"name\": \"nobody\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      },@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"email\": \"robot@gserviceaccount.com\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"name\": \"robot\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    ]@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@",
-      "@@@STEP_LOG_LINE@json.input@{@@@",
-      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"12345\"@@@",
-      "@@@STEP_LOG_LINE@json.input@}@@@",
-      "@@@STEP_LOG_END@json.input@@@",
-      "@@@STEP_LINK@gerrit link@https://pigweed-review.googlesource.com/q/12345@@@"
-    ]
-  },
-  {
-    "cmd": [],
-    "name": "roll message"
-  },
-  {
-    "cmd": [],
-    "name": "roll message.message for foo.txt",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@template@roll: {project_name}: {sanitized_message}@@@",
-      "@@@STEP_LOG_LINE@template@@@@",
-      "@@@STEP_LOG_LINE@template@{remote}@@@",
-      "@@@STEP_LOG_LINE@template@{project_name} Rolled-Commits: {old_revision:.15}..{new_revision:.15}@@@",
-      "@@@STEP_LOG_END@template@@@",
-      "@@@STEP_LOG_LINE@kwargs@'new_revision': '2d72510e447ab60a9728aeea2362d8be2cbd7789'@@@",
-      "@@@STEP_LOG_LINE@kwargs@'old_revision': '1111111111111111111111111111111111111111'@@@",
-      "@@@STEP_LOG_LINE@kwargs@'original_message': 'foo\\nbar\\n\\nChange-Id: I1111'@@@",
-      "@@@STEP_LOG_LINE@kwargs@'project_name': 'foo.txt'@@@",
-      "@@@STEP_LOG_LINE@kwargs@'remote': 'https://pigweed.googlesource.com/pigweed/pigweed'@@@",
-      "@@@STEP_LOG_LINE@kwargs@'sanitized_message': 'foo\\nbar\\n'@@@",
-      "@@@STEP_LOG_END@kwargs@@@",
-      "@@@STEP_LOG_LINE@message@roll: foo.txt: foo@@@",
-      "@@@STEP_LOG_LINE@message@bar@@@",
-      "@@@STEP_LOG_LINE@message@@@@",
-      "@@@STEP_LOG_LINE@message@@@@",
-      "@@@STEP_LOG_LINE@message@https://pigweed.googlesource.com/pigweed/pigweed@@@",
-      "@@@STEP_LOG_LINE@message@foo.txt Rolled-Commits: 111111111111111..2d72510e447ab60@@@",
-      "@@@STEP_LOG_END@message@@@"
-    ]
-  },
-  {
     "cmd": [
       "git",
       "ls-files",
@@ -2934,11 +1693,9 @@
       "git",
       "commit",
       "-m",
-      "roll: foo.txt: foo\nbar\n\n\nhttps://pigweed.googlesource.com/pigweed/pigweed\nfoo.txt Rolled-Commits: 111111111111111..2d72510e447ab60\n--divider--\nRoller-URL: https://ci.chromium.org/b/8945511751514863184\nCQ-Do-Not-Cancel-Tryjobs: true\nChange-Id: I520324b8ea4bfe51bb4d3690983686816663e896",
+      "roll: foo.txt 1111111..2d72510 (3 commits)\n\n3e30158:https://foo-review.googlesource.com/c/foo/+/12345 fake A msg 0\n3380b83:https://foo-review.googlesource.com/c/foo/+/12345 fake A msg 1\n363caa9:https://foo-review.googlesource.com/c/foo/+/12345 fake A msg 2\n--divider--\n\nRolled-Repo: https://foo.googlesource.com/foo\nRolled-Commits: 11111111111111..2d72510e447ab6\nRoller-URL: https://ci.chromium.org/b/8945511751514863184\nCQ-Do-Not-Cancel-Tryjobs: true\nChange-Id: I520324b8ea4bfe51bb4d3690983686816663e896",
       "-a",
-      "--no-verify",
-      "--author",
-      "author <author@pigweed.infra.roller.example.com>"
+      "--no-verify"
     ],
     "cwd": "[START_DIR]/co",
     "infra_step": true,
diff --git a/recipes/txt_roller.proto b/recipes/txt_roller.proto
index 9f26bfd..26d6b4b 100644
--- a/recipes/txt_roller.proto
+++ b/recipes/txt_roller.proto
@@ -33,4 +33,7 @@
 
   // Auto roller module options.
   recipe_modules.fuchsia.auto_roller.Options auto_roller_options = 4;
+
+  // Line of text to divide the commit header and body from the footers.
+  string commit_divider = 5;
 }
diff --git a/recipes/txt_roller.py b/recipes/txt_roller.py
index 019faaf..028efba 100644
--- a/recipes/txt_roller.py
+++ b/recipes/txt_roller.py
@@ -34,8 +34,9 @@
 
 DEPS = [
     'fuchsia/auto_roller',
+    'fuchsia/git_roll_util',
+    'fuchsia/gitiles',
     'pigweed/checkout',
-    'pigweed/roll_util',
     'pigweed/txt_roll',
     'recipe_engine/properties',
 ]
@@ -63,18 +64,22 @@
     if not rolls:
         return  # pragma: no cover
 
-    authors: Sequence[api.roll_util.Account] = api.roll_util.authors(*rolls)
-
-    author_override: Optional[api.roll_util.Account] = None
-    if len(authors) == 1 and props.forge_author:
-        author_override = attrs.asdict(
-            api.roll_util.fake_author(next(iter(authors)))
+    author_override: api.git_roll_util.Author = None
+    if props.forge_author:
+        author_override = api.git_roll_util.get_author_override(
+            *rolls,
+            domain_prefix="pigweed.infra.roller",
         )
 
-    change: api.auto_roller.GerritChange = api.auto_roller.attempt_roll(
+    change = api.auto_roller.attempt_roll(
         props.auto_roller_options,
         repo_dir=checkout.root,
-        commit_message=api.roll_util.message(*rolls),
+        commit_message=api.git_roll_util.format_commit_message(
+            *rolls,
+            roll_prefix="roll:",
+            send_comment=True,
+            commit_divider=props.commit_divider,
+        ),
         author_override=author_override,
     )
 
@@ -101,19 +106,13 @@
         )
         return api.properties(props)
 
-    def commit_data(name, **kwargs):
-        return api.roll_util.commit_data(
-            name,
-            api.roll_util.commit('a' * 40, 'foo\nbar\n\nChange-Id: I1111'),
-            **kwargs,
-        )
-
     yield api.test(
         'success',
-        properties(api.txt_roll.entry(path='foo.txt', remote=_url("foo"))),
-        api.roll_util.properties(commit_divider='--divider--'),
+        properties(
+            api.txt_roll.entry(path='foo.txt', remote=_url("foo")),
+            commit_divider='--divider--',
+        ),
         trigger('foo'),
-        api.roll_util.forward_roll(),
-        commit_data('foo.txt', prefix=''),
+        api.gitiles.log("log foo.txt", "A"),
         api.auto_roller.success(),
     )