txt_roller: Initial commit

Add a recipe to update a pin of a git repository that's held in a simple
text file instead of a submodule. This allows the revision to be
referenced but doesn't have the "submodule" appear in the checkout.

Bug: 589
Change-Id: I98332679e35a134b7e63b1c75694a6ecd99dcab5
Reviewed-on: https://pigweed-review.googlesource.com/c/infra/recipes/+/77302
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Commit-Queue: Rob Mohr <mohrr@google.com>
diff --git a/recipes/txt_roller.expected/backwards.json b/recipes/txt_roller.expected/backwards.json
new file mode 100644
index 0000000..285a572
--- /dev/null
+++ b/recipes/txt_roller.expected/backwards.json
@@ -0,0 +1,1210 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.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]/checkout",
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo",
+    "~followup_annotations": [
+      "@@@STEP_LINK@applied foo:1234@https://foo-review.googlesource.com/c/1234@@@"
+    ]
+  },
+  {
+    "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",
+    "~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 gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/tool_manifest.json",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.change data.ensure gerrit.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@{@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"path\": \"path/to/gerrit\",@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"version\": \"version:pinned-version\"@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@}@@@",
+      "@@@STEP_LOG_END@tool_manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.change data.ensure gerrit.install path/to/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.change data.ensure gerrit.install path/to/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version",
+      "-ensure-file",
+      "path/to/gerrit version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.change data.ensure gerrit.install path/to/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\": \"path/to/gerrit\"@@@",
+      "@@@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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:h3ll0\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "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@  }@@@",
+      "@@@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, branch='main', gerrit_name='foo', submitted=True, base=None, base_type=None)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/project"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://foo.googlesource.com/foo"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/foo.googlesource.com-foo"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
+    "infra_step": true,
+    "name": "checkout foo.cache.git init",
+    "~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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
+    "infra_step": true,
+    "name": "checkout foo.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
+    "infra_step": true,
+    "name": "checkout foo.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/project/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/foo.googlesource.com-foo/objects\n",
+      "[START_DIR]/project/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/foo.googlesource.com-foo/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.git rev-parse (2)",
+    "~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": [
+      "git",
+      "fetch",
+      "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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.apply foo:1234.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.apply foo:1234.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.git submodule status",
+    "~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, branch='main', gerrit_name='foo', submitted=True, base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash')]\nnot applied []@@@"
+    ]
+  },
+  {
+    "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",
+    "~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": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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]/checkout",
+    "name": "checkout foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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,
+    "name": "read old revision",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@foo.txt@1111111111111111111111111111111111111111@@@",
+      "@@@STEP_LOG_END@foo.txt@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "2222222222222222222222222222222222222222",
+      "[START_DIR]/checkout/foo.txt"
+    ],
+    "infra_step": true,
+    "name": "write new revision",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@foo.txt@2222222222222222222222222222222222222222@@@",
+      "@@@STEP_LOG_END@foo.txt@@@"
+    ]
+  },
+  {
+    "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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "merge-base",
+      "--is-ancestor",
+      "2222222222222222222222222222222222222222",
+      "1111111111111111111111111111111111111111"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "get roll direction.is backward",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "cancelling 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@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/txt_roller.expected/bad-trigger.json b/recipes/txt_roller.expected/bad-trigger.json
new file mode 100644
index 0000000..507b946
--- /dev/null
+++ b/recipes/txt_roller.expected/bad-trigger.json
@@ -0,0 +1,754 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[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 pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.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]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "failure": {
+      "failure": {},
+      "humanReason": "triggering repository (https://foo.googlesource.com/bar) does not match project remote (https://foo.googlesource.com/foo)"
+    },
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/txt_roller.expected/no-trigger.json b/recipes/txt_roller.expected/no-trigger.json
new file mode 100644
index 0000000..16412b5
--- /dev/null
+++ b/recipes/txt_roller.expected/no-trigger.json
@@ -0,0 +1,1543 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "name": "checkout pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.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]/checkout",
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo",
+    "~followup_annotations": [
+      "@@@STEP_LINK@applied foo:1234@https://foo-review.googlesource.com/c/1234@@@"
+    ]
+  },
+  {
+    "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",
+    "~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 gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/tool_manifest.json",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.change data.ensure gerrit.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@{@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"path\": \"path/to/gerrit\",@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"version\": \"version:pinned-version\"@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@}@@@",
+      "@@@STEP_LOG_END@tool_manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.change data.ensure gerrit.install path/to/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.change data.ensure gerrit.install path/to/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version",
+      "-ensure-file",
+      "path/to/gerrit version:pinned-version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.change data.ensure gerrit.install path/to/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\": \"path/to/gerrit\"@@@",
+      "@@@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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:h3ll0\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "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@  }@@@",
+      "@@@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, branch='main', gerrit_name='foo', submitted=True, base=None, base_type=None)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/project"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://foo.googlesource.com/foo"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/foo.googlesource.com-foo"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
+    "infra_step": true,
+    "name": "checkout foo.cache.git init",
+    "~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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
+    "infra_step": true,
+    "name": "checkout foo.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/foo.googlesource.com-foo",
+    "infra_step": true,
+    "name": "checkout foo.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/project/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/foo.googlesource.com-foo/objects\n",
+      "[START_DIR]/project/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/foo.googlesource.com-foo/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[START_DIR]/project",
+    "infra_step": true,
+    "name": "checkout foo.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.git rev-parse (2)",
+    "~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": [
+      "git",
+      "fetch",
+      "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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.apply foo:1234.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.apply foo:1234.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "checkout foo.git submodule status",
+    "~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, branch='main', gerrit_name='foo', submitted=True, base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash')]\nnot applied []@@@"
+    ]
+  },
+  {
+    "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",
+    "~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": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/snapshot"
+    ],
+    "infra_step": true,
+    "name": "checkout foo.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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]/checkout",
+    "name": "checkout foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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,
+    "name": "read old revision",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@foo.txt@1111111111111111111111111111111111111111@@@",
+      "@@@STEP_LOG_END@foo.txt@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "2222222222222222222222222222222222222222",
+      "[START_DIR]/checkout/foo.txt"
+    ],
+    "infra_step": true,
+    "name": "write new revision",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@foo.txt@2222222222222222222222222222222222222222@@@",
+      "@@@STEP_LOG_END@foo.txt@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "get roll direction",
+    "~followup_annotations": [
+      "@@@STEP_SUMMARY_TEXT@forward@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "merge-base",
+      "--is-ancestor",
+      "1111111111111111111111111111111111111111",
+      "2222222222222222222222222222222222222222"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "get roll direction.is forward",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "merge-base",
+      "--is-ancestor",
+      "2222222222222222222222222222222222222222",
+      "1111111111111111111111111111111111111111"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "get roll direction.is backward",
+    "~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",
+    "name": "remote.name",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "get-url",
+      "origin"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "remote.url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "foo.txt"
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%H\n%ae\n%B",
+      "-z",
+      "1111111111111111111111111111111111111111..2222222222222222222222222222222222222222"
+    ],
+    "cwd": "[START_DIR]/project",
+    "name": "foo.txt.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/project",
+    "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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"12345\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/project",
+    "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\": \"owner@example.com\"@@@",
+      "@@@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@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"email\": \"nobody@google.com\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"email\": \"robot@gserviceaccount.com\"@@@",
+      "@@@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': '2222222222222222222222222222222222222222'@@@",
+      "@@@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..222222222222222@@@",
+      "@@@STEP_LOG_END@message@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "ls-files",
+      "--modified",
+      "--deleted",
+      "--exclude-standard"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "name": "check for no-op commit",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@hello@@@",
+      "@@@STEP_LOG_END@stdout@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "add",
+      "--update"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "git add"
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "git rev-parse"
+  },
+  {
+    "cmd": [],
+    "name": "calculate Change-Id",
+    "~followup_annotations": [
+      "@@@STEP_TEXT@Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "diff",
+      "--unified=0",
+      "--cached"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "calculate Change-Id.git diff",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@diff --git a/foo.txt b/foo.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@--- a/foo.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+++ b/foo.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@@@ -16 +16 @@@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@-        foo = 5@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+        foo = 6@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@diff --git a/bar.txt b/bar.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@--- a/bar.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+++ b/bar.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@@@ -5 +5 @@@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@-        bar = 0@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+        bar = 1@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@@@@",
+      "@@@STEP_LOG_END@diff (without hashes)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "hash-object",
+      "diff --git a/foo.txt b/foo.txt\n--- a/foo.txt\n+++ b/foo.txt\n@@ -16 +16 @@\n-        foo = 5\n+        foo = 6\ndiff --git a/bar.txt b/bar.txt\n--- a/bar.txt\n+++ b/bar.txt\n@@ -5 +5 @@\n-        bar = 0\n+        bar = 1\n############"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "calculate Change-Id.git hash-object",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\", \"MESSAGES\"], \"q\": \"change:pigweed/pigweed~main~Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "check for identical roll",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@null@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"o\": [@@@",
+      "@@@STEP_LOG_LINE@json.input@      \"CURRENT_COMMIT\", @@@",
+      "@@@STEP_LOG_LINE@json.input@      \"CURRENT_REVISION\", @@@",
+      "@@@STEP_LOG_LINE@json.input@      \"MESSAGES\"@@@",
+      "@@@STEP_LOG_LINE@json.input@    ], @@@",
+      "@@@STEP_LOG_LINE@json.input@    \"q\": \"change:pigweed/pigweed~main~Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad\"@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "commit",
+      "-m",
+      "[roll foo.txt] foo\nbar\n\n\nhttps://pigweed.googlesource.com/pigweed/pigweed\nfoo.txt Rolled-Commits: 111111111111111..222222222222222\nRoller-URL: https://ci.chromium.org/b/0\nCq-Cl-Tag: roller-builder:\nCq-Cl-Tag: roller-bid:0\nCQ-Do-Not-Cancel-Tryjobs: true\nChange-Id: Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad",
+      "-a"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "git commit"
+  },
+  {
+    "cmd": [
+      "git",
+      "push",
+      "--push-option",
+      "nokeycheck",
+      "origin",
+      "HEAD:refs/for/main%l=Commit-Queue+2"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "name": "git push",
+    "timeout": 180.0,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LINK@gerrit link@https://pigweed-review.googlesource.com/q/pigweed/pigweed~main~Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "check for completion"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"pigweed/pigweed~main~Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad\", \"params\": {\"o\": [\"CURRENT_REVISION\"]}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "check for completion.check if done (0)",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"abc123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"labels\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"Commit-Queue\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"approved\": {}@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"MERGED\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"pigweed/pigweed~main~Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad\", @@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"o\": [@@@",
+      "@@@STEP_LOG_LINE@json.input@      \"CURRENT_REVISION\"@@@",
+      "@@@STEP_LOG_LINE@json.input@    ]@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://pigweed-review.googlesource.com/q/pigweed/pigweed~main~Ib92a62842ab6b0cc6777dcd33fb1c8532c7396ad@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/txt_roller.expected/success.json b/recipes/txt_roller.expected/success.json
new file mode 100644
index 0000000..35959b9
--- /dev/null
+++ b/recipes/txt_roller.expected/success.json
@@ -0,0 +1,2473 @@
+[
+  {
+    "cmd": [],
+    "name": "checkout pigweed"
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "remote",
+      "add",
+      "origin",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "remote.origin.url",
+      "https://pigweed.googlesource.com/pigweed/pigweed"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remote set-url",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "fetch.uriprotocols",
+      "https"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "cwd": "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/checkout/.git/objects/info"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects\n",
+      "[START_DIR]/checkout/.git/objects/info/alternates"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/pigweed.googlesource.com-pigweed-pigweed/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--recurse-submodules"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout pigweed.git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--oneline",
+      "-n",
+      "10"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.git log.[START_DIR]/checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[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 pigweed.mkdir",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "status",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.submodule-status",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.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]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout pigweed.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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 pigweed.write git log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_END@git.log@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo",
+    "~followup_annotations": [
+      "@@@STEP_LINK@applied foo:1234@https://foo-review.googlesource.com/c/1234@@@"
+    ]
+  },
+  {
+    "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 gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@3@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "RECIPE_MODULE[fuchsia::gerrit]/resources/tool_manifest.json",
+      "/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 gerrit.read manifest",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@{@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"path\": \"path/to/gerrit\",@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@  \"version\": \"version:pinned-version\"@@@",
+      "@@@STEP_LOG_LINE@tool_manifest.json@}@@@",
+      "@@@STEP_LOG_END@tool_manifest.json@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.change data.process gitiles commit.ensure gerrit.install path/to/gerrit",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@4@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version"
+    ],
+    "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 gerrit.install path/to/gerrit.ensure package directory",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@5@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version",
+      "-ensure-file",
+      "path/to/gerrit 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 gerrit.install path/to/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\": \"path/to/gerrit\"@@@",
+      "@@@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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://foo-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:2d72510e447ab60a9728aeea2362d8be2cbd7789\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "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@  }@@@",
+      "@@@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, branch='main', gerrit_name='foo', submitted=True, base=None, base_type=None)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[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.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "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 init",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "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 remote",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "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.set fetch.uriprotocols",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.cache",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "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.cache.write guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[CACHE]/git/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.cache.makedirs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "init",
+      "--bare"
+    ],
+    "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",
+    "~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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "config",
+      "--replace-all",
+      "remote.origin.fetch",
+      "+refs/heads/*:refs/heads/*",
+      "\\+refs/heads/\\*:.*"
+    ],
+    "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.replace fetch configs",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--prune",
+      "--tags",
+      "origin"
+    ],
+    "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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/project/.git/objects/info"
+    ],
+    "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.cache.makedirs object/info",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[CACHE]/git/foo.googlesource.com-foo/objects\n",
+      "[START_DIR]/project/.git/objects/info/alternates"
+    ],
+    "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.cache.alternates",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_LOG_LINE@alternates@[CACHE]/git/foo.googlesource.com-foo/objects@@@",
+      "@@@STEP_LOG_END@alternates@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[CACHE]/git/.GUARD_FILE"
+    ],
+    "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.cache.remove guard file",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "--tags",
+      "origin",
+      "main",
+      "--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.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "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 rev-parse",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "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 clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout foo.submodule",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "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.submodule.git submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive",
+      "--jobs",
+      "4"
+    ],
+    "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.submodule.git submodule update",
+    "~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 (2)",
+    "~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": [
+      "git",
+      "fetch",
+      "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",
+    "~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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--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.apply foo:1234.git submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "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.git submodule status",
+    "~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, branch='main', gerrit_name='foo', submitted=True, base='HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_HEAD_', base_type='submitted_commit_hash')]\nnot applied []@@@"
+    ]
+  },
+  {
+    "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",
+    "~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": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[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": [
+      "vpython",
+      "-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]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "checkout foo.log",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-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": [
+      "vpython",
+      "-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@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "2d72510e447ab60a9728aeea2362d8be2cbd7789",
+      "[START_DIR]/checkout/foo.txt"
+    ],
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "write new revision",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@foo.txt@2d72510e447ab60a9728aeea2362d8be2cbd7789@@@",
+      "@@@STEP_LOG_END@foo.txt@@@"
+    ]
+  },
+  {
+    "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",
+    "~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",
+    "~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",
+    "~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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "foo.txt"
+  },
+  {
+    "cmd": [
+      "git",
+      "log",
+      "--pretty=format:%H\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",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"q\": \"commit:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "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.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/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"12345\"}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "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.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\": \"owner@example.com\"@@@",
+      "@@@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@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"email\": \"nobody@google.com\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }, @@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"email\": \"robot@gserviceaccount.com\"@@@",
+      "@@@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",
+      "--modified",
+      "--deleted",
+      "--exclude-standard"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "check for no-op commit",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@hello@@@",
+      "@@@STEP_LOG_END@stdout@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "add",
+      "--update"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "git add"
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "git rev-parse"
+  },
+  {
+    "cmd": [],
+    "name": "calculate Change-Id",
+    "~followup_annotations": [
+      "@@@STEP_TEXT@I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "diff",
+      "--unified=0",
+      "--cached"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "calculate Change-Id.git diff",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@diff --git a/foo.txt b/foo.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@--- a/foo.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+++ b/foo.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@@@ -16 +16 @@@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@-        foo = 5@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+        foo = 6@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@diff --git a/bar.txt b/bar.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@--- a/bar.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+++ b/bar.txt@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@@@ -5 +5 @@@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@-        bar = 0@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@+        bar = 1@@@",
+      "@@@STEP_LOG_LINE@diff (without hashes)@@@@",
+      "@@@STEP_LOG_END@diff (without hashes)@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "hash-object",
+      "diff --git a/foo.txt b/foo.txt\n--- a/foo.txt\n+++ b/foo.txt\n@@ -16 +16 @@\n-        foo = 5\n+        foo = 6\ndiff --git a/bar.txt b/bar.txt\n--- a/bar.txt\n+++ b/bar.txt\n@@ -5 +5 @@\n-        bar = 0\n+        bar = 1\n####builder########"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "calculate Change-Id.git hash-object",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-query",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"params\": {\"o\": [\"CURRENT_COMMIT\", \"CURRENT_REVISION\", \"MESSAGES\"], \"q\": \"change:pigweed/pigweed~main~I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c\"}}",
+      "-output",
+      "/path/to/tmp/json"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "check for identical roll",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@null@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"o\": [@@@",
+      "@@@STEP_LOG_LINE@json.input@      \"CURRENT_COMMIT\", @@@",
+      "@@@STEP_LOG_LINE@json.input@      \"CURRENT_REVISION\", @@@",
+      "@@@STEP_LOG_LINE@json.input@      \"MESSAGES\"@@@",
+      "@@@STEP_LOG_LINE@json.input@    ], @@@",
+      "@@@STEP_LOG_LINE@json.input@    \"q\": \"change:pigweed/pigweed~main~I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c\"@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "commit",
+      "-m",
+      "[roll foo.txt] foo\nbar\n\n\nhttps://pigweed.googlesource.com/pigweed/pigweed\nfoo.txt Rolled-Commits: 111111111111111..2d72510e447ab60\nRoller-URL: https://ci.chromium.org/b/8945511751514863184\nCq-Cl-Tag: roller-builder:builder\nCq-Cl-Tag: roller-bid:8945511751514863184\nCQ-Do-Not-Cancel-Tryjobs: true\nChange-Id: I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c",
+      "-a"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "git commit"
+  },
+  {
+    "cmd": [
+      "git",
+      "push",
+      "--push-option",
+      "nokeycheck",
+      "origin",
+      "HEAD:refs/for/main%l=Commit-Queue+2"
+    ],
+    "cwd": "[START_DIR]/checkout",
+    "infra_step": true,
+    "luci_context": {
+      "realm": {
+        "name": "project:ci"
+      },
+      "resultdb": {
+        "current_invocation": {
+          "name": "invocations/build:8945511751514863184",
+          "update_token": "token"
+        },
+        "hostname": "rdbhost"
+      }
+    },
+    "name": "git push",
+    "timeout": 180.0,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LINK@gerrit link@https://pigweed-review.googlesource.com/q/pigweed/pigweed~main~I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "check for completion"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cipd_tool/path/to/gerrit/version%3Apinned-version/gerrit",
+      "change-detail",
+      "-host",
+      "https://pigweed-review.googlesource.com",
+      "-input",
+      "{\"change_id\": \"pigweed/pigweed~main~I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c\", \"params\": {\"o\": [\"CURRENT_REVISION\"]}}",
+      "-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": "check for completion.check if done (0)",
+    "timeout": 600,
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"current_revision\": \"abc123\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"labels\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"Commit-Queue\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"approved\": {}@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"status\": \"MERGED\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@STEP_LOG_LINE@json.input@{@@@",
+      "@@@STEP_LOG_LINE@json.input@  \"change_id\": \"pigweed/pigweed~main~I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c\", @@@",
+      "@@@STEP_LOG_LINE@json.input@  \"params\": {@@@",
+      "@@@STEP_LOG_LINE@json.input@    \"o\": [@@@",
+      "@@@STEP_LOG_LINE@json.input@      \"CURRENT_REVISION\"@@@",
+      "@@@STEP_LOG_LINE@json.input@    ]@@@",
+      "@@@STEP_LOG_LINE@json.input@  }@@@",
+      "@@@STEP_LOG_LINE@json.input@}@@@",
+      "@@@STEP_LOG_END@json.input@@@",
+      "@@@STEP_LINK@gerrit link@https://pigweed-review.googlesource.com/q/pigweed/pigweed~main~I28135185e8ac8c69d0b894c4be54ddbb0ea69d5c@@@"
+    ]
+  },
+  {
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipes/txt_roller.proto b/recipes/txt_roller.proto
new file mode 100644
index 0000000..3c83ffb
--- /dev/null
+++ b/recipes/txt_roller.proto
@@ -0,0 +1,35 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+syntax = "proto3";
+
+package recipes.pigweed.txt_roller;
+
+message InputProperties {
+  // The path of the submodule to update. Required.
+  string txt_path = 1;
+
+  // Repository referred to by the text file revision.
+  string project_remote = 2;
+
+  // Branch to get latest from when new_revision is None and no buildbucket
+  // trigger. Default: "main".
+  string project_branch = 3;
+
+  // Don't do anything that has external effects.
+  bool dry_run = 4;
+
+  // Vote Bot-Commit+1 instead of Code-Review+2.
+  bool bot_commit = 5;
+}
diff --git a/recipes/txt_roller.py b/recipes/txt_roller.py
new file mode 100644
index 0000000..d1bfc41
--- /dev/null
+++ b/recipes/txt_roller.py
@@ -0,0 +1,183 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Roll a pin of a git repository in a simple text file."""
+
+import re
+
+import attr
+from PB.recipes.pigweed.txt_roller import InputProperties
+from recipe_engine import post_process
+from six.moves import configparser
+from six.moves import urllib
+from six import StringIO
+
+DEPS = [
+    'fuchsia/auto_roller',
+    'fuchsia/status_check',
+    'pigweed/checkout',
+    'pigweed/roll_util',
+    'recipe_engine/buildbucket',
+    'recipe_engine/file',
+    'recipe_engine/path',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+]
+
+PROPERTIES = InputProperties
+
+PYTHON_VERSION_COMPATIBILITY = "PY3"
+
+
+def RunSteps(api, props):  # pylint: disable=invalid-name
+    txt_path = props.txt_path
+    project_remote = props.project_remote
+    project_branch = props.project_branch or 'main'
+
+    # The checkout module will try to use trigger data to pull in a specific
+    # patch. Since the triggering commit is in a different repository that
+    # needs to be disabled.
+    api.checkout(use_trigger=False)
+
+    new_revision = None
+
+    # First, try to get new_revision from the trigger.
+    bb_remote = None
+    commit = api.buildbucket.build.input.gitiles_commit
+    if commit and commit.project:
+        new_revision = commit.id
+        host = commit.host
+        bb_remote = 'https://{}/{}'.format(host, commit.project)
+
+    # If we still don't have a revision then it wasn't in the trigger. (Perhaps
+    # this was manually triggered.) In this case we update to the
+    # property-specified branch HEAD.
+    if new_revision is None:
+        new_revision = project_branch
+
+    # If this was triggered by a gitiles poller, check that the triggering
+    # repository matches project_remote.
+
+    if bb_remote:
+        if not api.checkout.remotes_equivalent(project_remote, bb_remote):
+            raise api.step.StepFailure(
+                'triggering repository ({}) does not match project remote '
+                '({})'.format(bb_remote, project_remote)
+            )
+
+    project_dir = api.path['start_dir'].join('project')
+    api.checkout(project_remote, branch=project_branch, 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 = api.checkout.get_revision(
+            project_dir, 'get new revision', test_data='2' * 40
+        )
+
+    full_txt_path = api.checkout.root.join(txt_path)
+
+    old_revision = api.file.read_text(
+        'read old revision', full_txt_path, test_data='1' * 40,
+    ).strip()
+
+    api.file.write_text('write new revision', full_txt_path, new_revision)
+
+    direction = api.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 api.roll_util.can_roll(direction):
+        api.roll_util.skip_roll_step(project_remote, old_revision, new_revision)
+        return
+
+    with api.step.nest('txt_path') as pres:
+        pres.step_summary_text = repr(txt_path)
+
+    rolls = {
+        txt_path: api.roll_util.Roll(
+            project_name=txt_path,
+            old_revision=old_revision,
+            new_revision=new_revision,
+            proj_dir=project_dir,
+            direction=direction,
+        ),
+    }
+
+    change = api.auto_roller.attempt_roll(
+        gerrit_host=api.checkout.gerrit_host(),
+        gerrit_project=api.checkout.gerrit_project(),
+        upstream_ref=api.checkout.branch,
+        repo_dir=api.checkout.root,
+        commit_message=api.roll_util.message(*rolls.values()),
+        dry_run=props.dry_run,
+        labels_to_set=api.roll_util.labels_to_set,
+        labels_to_wait_on=api.roll_util.labels_to_wait_on,
+        bot_commit=props.bot_commit,
+    )
+
+    return api.auto_roller.raw_result(change)
+
+
+def GenTests(api):  # pylint: disable=invalid-name
+    """Create tests."""
+
+    def _url(x):
+        assert ':' not in x
+        return 'https://foo.googlesource.com/' + x
+
+    def trigger(url, **kwargs):
+        return api.checkout.ci_test_data(git_repo=_url(url), **kwargs)
+
+    def properties(**kwargs):
+        new_kwargs = api.checkout.git_properties()
+        new_kwargs.update(kwargs)
+        return api.properties(**new_kwargs)
+
+    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.status_check.test('success')
+        + properties(txt_path='foo.txt', project_remote=_url("foo"))
+        + trigger('foo')
+        + api.roll_util.forward_roll()
+        + commit_data('foo.txt', prefix='')
+        + api.auto_roller.success()
+    )
+
+    yield (
+        api.status_check.test('bad-trigger', status='failure')
+        + properties(txt_path='foo.txt', project_remote=_url("foo"))
+        + trigger('bar')
+    )
+
+    yield (
+        api.status_check.test('no-trigger')
+        + properties(txt_path='foo.txt', project_remote=_url("foo"))
+        + api.roll_util.forward_roll()
+        + commit_data('foo.txt', prefix='')
+        + api.auto_roller.success()
+    )
+
+    yield (
+        api.status_check.test('backwards')
+        + properties(txt_path='foo.txt', project_remote=_url("foo"))
+        + api.roll_util.backward_roll()
+    )