[submodule_update] Migrate tests from google3

submodule_test.go is copied from
http://google3/turquoise/infra/foundation/go/submodule_update/submodule/submodule_test.go
with necessary changes to support building with Go's build system.

Also fix minor issues in some other files.

Bug: 320755417
Change-Id: Iead7c68f469f0fb6a76bf25b144971d2b0b7a065
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/infra/+/979912
Reviewed-by: Ina Huh <ihuh@google.com>
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/cmd/submodule_update/gitutil/git.go b/cmd/submodule_update/gitutil/git.go
index 7014c9c..09916c8 100644
--- a/cmd/submodule_update/gitutil/git.go
+++ b/cmd/submodule_update/gitutil/git.go
@@ -1,6 +1,7 @@
 // Copyright 2023 The Fuchsia Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 // Package gitutil is a wrapper layer for handing calls to the git tool.
 package gitutil
 
@@ -38,16 +39,15 @@
 
 // Error outputs GitError struct as a string.
 func (ge GitError) Error() string {
-	result := fmt.Sprintf("(%s)", ge.Root)
-	result += "'git "
-	result += strings.Join(ge.Args, " ")
-	result += "' failed:\n"
-	result += "stdout:\n"
-	result += ge.Output + "\n"
-	result += "stderr:\n"
-	result += ge.ErrorOutput
-	result += "\ncommand fail error: " + ge.err.Error()
-	return result
+	lines := []string{
+		fmt.Sprintf("(%s) 'git %s' failed:", ge.Root, strings.Join(ge.Args, " ")),
+		"stdout:",
+		ge.Output,
+		"stderr:",
+		ge.ErrorOutput,
+		"command fail error: " + ge.err.Error(),
+	}
+	return strings.Join(lines, "\n")
 }
 
 // Git structure for environmental options passed to git tool.
@@ -207,9 +207,7 @@
 // SubmoduleAdd adds submodule to current branch.
 func (g *Git) SubmoduleAdd(remote string, path string) error {
 	// Use -f to add submodules even if in .gitignore.
-	args := []string{"submodule", "add", "-f"}
-	args = append(args, remote, path)
-	return g.run(args...)
+	return g.run("submodule", "add", "-f", remote, path)
 }
 
 // SubmoduleStatus returns current current status of submodules in a superproject.
@@ -244,6 +242,54 @@
 	args = append(args, "--jobs=50")
 	args = append(args, paths...)
 	return g.run(args...)
+
+}
+
+// Clone clones the given repository to the given local path.  If reference is
+// not empty it uses the given path as a reference/shared repo.
+func (g *Git) Clone(repo, path string, opts ...CloneOpt) error {
+	args := []string{"clone"}
+	for _, opt := range opts {
+		switch typedOpt := opt.(type) {
+		case BareOpt:
+			if typedOpt {
+				args = append(args, "--bare")
+			}
+		case ReferenceOpt:
+			reference := string(typedOpt)
+			if reference != "" {
+				args = append(args, []string{"--reference-if-able", reference}...)
+			}
+		case SharedOpt:
+			if typedOpt {
+				args = append(args, []string{"--shared", "--local"}...)
+			}
+		case NoCheckoutOpt:
+			if typedOpt {
+				args = append(args, "--no-checkout")
+			}
+		case DepthOpt:
+			if typedOpt > 0 {
+				args = append(args, []string{"--depth", strconv.Itoa(int(typedOpt))}...)
+			}
+		case OmitBlobsOpt:
+			if typedOpt {
+				args = append(args, "--filter=blob:none")
+			}
+		case OffloadPackfilesOpt:
+			if typedOpt {
+				args = append([]string{"-c", "fetch.uriprotocols=https"}, args...)
+			}
+		case RecurseSubmodulesOpt:
+			// TODO(iankaz): Add setting submodule.fetchJobs in git config to jiri init
+			if typedOpt {
+				args = append(args, []string{"--recurse-submodules", "--jobs=16"}...)
+			}
+		}
+	}
+	args = append(args, repo)
+	args = append(args, path)
+	return g.run(args...)
 }
 
 // CommitWithMessage commits all files in staging with the given
diff --git a/cmd/submodule_update/submodule/submodule.go b/cmd/submodule_update/submodule/submodule.go
index 35967e4..0887a72 100644
--- a/cmd/submodule_update/submodule/submodule.go
+++ b/cmd/submodule_update/submodule/submodule.go
@@ -1,6 +1,7 @@
 // Copyright 2023 The Fuchsia Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 // Package submodule handles analyzing and updating git submodule states.
 package submodule
 
diff --git a/cmd/submodule_update/submodule/submodule_test.go b/cmd/submodule_update/submodule/submodule_test.go
new file mode 100644
index 0000000..6acc022
--- /dev/null
+++ b/cmd/submodule_update/submodule/submodule_test.go
@@ -0,0 +1,507 @@
+// Copyright 2024 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package submodule
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"go.fuchsia.dev/infra/cmd/submodule_update/gitutil"
+)
+
+// createTestdataTemp copies the testdata to a temp folder compatible with git.
+func createTestdataTemp(t *testing.T) (string, error) {
+	t.Helper()
+	uniqTempDir := t.TempDir()
+	fromPath := "../testdata"
+	if err := exec.Command("cp", "-R", fromPath, uniqTempDir).Run(); err != nil {
+		t.Fatalf("Error copying from %s to %s (%q)", fromPath, uniqTempDir, err)
+		return "", err
+	}
+	return uniqTempDir, nil
+}
+
+// createSuperproject clones the testdata superproject to a temp directory.
+func createSuperproject(t *testing.T, recurseSubmodules bool) (g *gitutil.Git, err error) {
+	t.Helper()
+	superprojectRemoteTemp, err := createTestdataTemp(t)
+	if err != nil {
+		t.Fatalf("Failed to create superproject remote temp dir: %s", err)
+		return nil, err
+	}
+
+	superprojectRoot := t.TempDir()
+	scm := gitutil.New(gitutil.RootDirOpt(superprojectRoot))
+	repo := filepath.Join(superprojectRemoteTemp, "testdata", "remote", "super")
+	if err := scm.Clone(repo, superprojectRoot, gitutil.RecurseSubmodulesOpt(recurseSubmodules)); err != nil {
+		t.Fatalf("Failed to clone superproject: %s", err)
+		return nil, err
+	}
+
+	gitConfig := map[string]string{
+		// Allow cloning local repos, since this is required for tests that use
+		// this mock data.
+		"protocol.file.allow": "always",
+	}
+	t.Setenv("GIT_CONFIG_COUNT", strconv.Itoa(len(gitConfig)))
+	i := 0
+	for k, v := range gitConfig {
+		t.Setenv(fmt.Sprintf("GIT_CONFIG_KEY_%d", i), k)
+		t.Setenv(fmt.Sprintf("GIT_CONFIG_VALUE_%d", i), v)
+		i++
+	}
+
+	return scm, nil
+}
+
+func createSubmodules(t *testing.T, submodules map[string]string) Submodules {
+	t.Helper()
+	var subModules = Submodules{}
+	for subPath, rev := range submodules {
+		subM := Submodule{
+			Path:     subPath,
+			Revision: rev,
+			// For testing, fake the remote
+			Remote: filepath.Join("remote", subPath),
+		}
+		subModules[subM.Key()] = subM
+	}
+	return subModules
+}
+
+func getSubmodules(t *testing.T, g *gitutil.Git, cached bool) Submodules {
+	t.Helper()
+	gitSubmodules, err := gitSubmodules(g, cached)
+	if err != nil {
+		t.Fatalf("Git submodules creation failed: %s", err)
+	}
+	return gitSubmodules
+}
+
+func TestSubmoduleDiff(t *testing.T) {
+	initialSub := createSubmodules(t, map[string]string{"sub1": "12345a", "sub2": "12345b"})
+	// Change remote for "sub1" to "sub1_new_remote"
+	remoteChangeSub := createSubmodules(t, map[string]string{"sub1": "12345a", "sub2": "12345b"})
+	remoteSub1 := remoteChangeSub[Key("sub1")]
+	remoteSub1.Remote = path.Join("new", "remote", "sub1")
+	remoteChangeSub[Key("sub1")] = remoteSub1
+
+	emptyDiff := Diff{}
+	type diffTest struct {
+		sub1 Submodules
+		sub2 Submodules
+		want Diff
+	}
+	testCases := []diffTest{
+		// No diff
+		{
+			initialSub,
+			initialSub,
+			emptyDiff,
+		},
+		// Add sub3
+		{
+			initialSub,
+			createSubmodules(t, map[string]string{"sub1": "12345a", "sub2": "12345b", "sub3": "12345c"}),
+			Diff{
+				NewSubmodules: []DiffSubmodule{{
+					Path:     "sub3",
+					Revision: "12345c",
+					Remote:   path.Join("remote", "sub3"),
+				}},
+			},
+		},
+		// Delete sub1
+		{
+			initialSub,
+			createSubmodules(t, map[string]string{"sub2": "12345b"}),
+			Diff{
+				DeletedSubmodules: []DiffSubmodule{{
+					Path:     "sub1",
+					Revision: "12345a",
+					Remote:   path.Join("remote", "sub1")}},
+			},
+		},
+		// Update sub1 (new revision)
+		{
+			initialSub,
+			createSubmodules(t, map[string]string{"sub1": "12346a", "sub2": "12345b"}),
+			Diff{
+				UpdatedSubmodules: []DiffSubmodule{{
+					Path:        "sub1",
+					Revision:    "12346a",
+					OldRevision: "12345a",
+				}},
+			},
+		},
+		// Move sub1 (change path) - Processed as a delete (old path) and a new submodule (new path)
+		{
+			initialSub,
+			createSubmodules(t, map[string]string{"sub3": "12345a", "sub2": "12345b"}),
+			Diff{
+				NewSubmodules: []DiffSubmodule{{
+					Path:     "sub3",
+					Revision: "12345a",
+					Remote:   path.Join("remote", "sub3")}},
+				DeletedSubmodules: []DiffSubmodule{{
+					Path:     "sub1",
+					Revision: "12345a",
+					Remote:   path.Join("remote", "sub1")}},
+			},
+		},
+		// Move sub1 (change remote) - Processed as a delete (old remote) and a new submodule (new remote)
+		{
+			initialSub,
+			remoteChangeSub,
+			Diff{
+				NewSubmodules: []DiffSubmodule{{
+					Path:     "sub1",
+					Revision: "12345a",
+					Remote:   path.Join("new", "remote", "sub1")}},
+				DeletedSubmodules: []DiffSubmodule{{
+					Path:     "sub1",
+					Revision: "12345a",
+					Remote:   path.Join("remote", "sub1")}},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		got, err := getDiff(tc.sub1, tc.sub2)
+		if err != nil {
+			t.Fatalf("GetDiff failed with: %s", err)
+		}
+
+		if diff := cmp.Diff(tc.want, got); diff != "" {
+			t.Errorf("GetDiff failed (-want +got):\n%s", diff)
+		}
+	}
+}
+
+func TestUpdateCommitMessage(t *testing.T) {
+	type msgTest struct {
+		message string
+		want    string
+	}
+	testCases := []msgTest{
+		// No [roll]
+		{
+			message: "Hello\nworld",
+			want:    "Hello\nworld",
+		},
+		// [roll] not at start
+		{
+			message: "Hello\nworld [roll] ",
+			want:    "Hello\nworld [roll] ",
+		},
+		// [roll] at start
+		{
+			message: "[roll] Hello\nworld",
+			want:    "[superproject] Hello\nworld",
+		},
+	}
+	for _, tc := range testCases {
+		got := updateCommitMessage(tc.message)
+		if diff := cmp.Diff(tc.want, got); diff != "" {
+			t.Errorf("updateCommitMessage failed (-want +got):\n%s", diff)
+		}
+	}
+}
+
+var gitSHA1Re = regexp.MustCompile(`^[0-9a-f]{40}$`)
+
+func TestSubmoduleStatusParsing(t *testing.T) {
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Git superproject creation failed: %s", err)
+	}
+
+	gitSubmodules := getSubmodules(t, gitSuperproject, true)
+
+	expected := []Submodule{
+		{
+			Name:     "sub1",
+			Revision: "rev1",
+			Path:     "sub1",
+			Remote:   "../sub1/",
+		},
+		{
+			Name:     "sub2",
+			Revision: "rev2",
+			Path:     "sub2",
+			Remote:   "../sub2/",
+		},
+	}
+	for _, subM := range expected {
+		if submodule, ok := gitSubmodules[Key(subM.Path)]; ok {
+			if subM.Remote != submodule.Remote {
+				t.Errorf("Remote (%s) didn't match expected %s", submodule.Remote, subM.Remote)
+			}
+			if !gitSHA1Re.MatchString(submodule.Revision) {
+				t.Errorf("Revision (%s) didn't match SHA1 regex", submodule.Revision)
+			}
+		} else {
+			t.Errorf("Expected submodule %s missing", subM.Path)
+		}
+	}
+}
+
+func TestJiriProjectParsing(t *testing.T) {
+	jiriProjectsPath := filepath.Join("..", "testdata", "jiri_projects_public.json")
+
+	jiriSubmodules, err := jiriProjectsToSubmodule(jiriProjectsPath)
+	if err != nil {
+		t.Fatalf("Jiri project parsing failed: %s", err)
+	}
+	expectedPaths := []string{"sub1", "sub2"}
+	for _, path := range expectedPaths {
+		if submodule, ok := jiriSubmodules[Key(path)]; ok {
+			if !gitSHA1Re.MatchString(submodule.Revision) {
+				t.Errorf("Revision (%s) didn't match SHA1 regex", submodule.Revision)
+			}
+			if submodule.Remote == "" {
+				t.Errorf("Expected remote for submodule %s not present", submodule.Path)
+			}
+		} else {
+			t.Errorf("Expected submodule %s missing", path)
+		}
+	}
+}
+
+func TestUpdateSubmodules(t *testing.T) {
+	subPath := "sub1"
+	wantRev := "4c473777b21946176bc7d157d195a29c108da6c6"
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	diff := []DiffSubmodule{{
+		Path:     subPath,
+		Revision: wantRev,
+	}}
+	if err := updateSubmodules(gitSuperproject, diff, gitSuperproject.RootDir()); err != nil {
+		cmd := exec.Command("tree", gitSuperproject.RootDir())
+		cmd.Stdout = os.Stdout
+		if e := cmd.Run(); e != nil {
+			t.Fatal(e)
+		}
+		t.Fatalf("Failed to update submodules: %s", err)
+	}
+	gitSubmodules := getSubmodules(t, gitSuperproject, false)
+	gotRev := gitSubmodules[Key(subPath)].Revision
+	if gotRev != wantRev {
+		t.Errorf("Expected %s for submodule revision, got %s", wantRev, gotRev)
+	}
+}
+
+func TestUpdateSubmodulesEmpty(t *testing.T) {
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	want := getSubmodules(t, gitSuperproject, false)
+	diff := Diff{}
+	if err := updateSubmodules(gitSuperproject, diff.UpdatedSubmodules, "tmp/superproject"); err != nil {
+		t.Fatalf("Failed to update submodules: %s", err)
+	}
+	got := getSubmodules(t, gitSuperproject, false)
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Errorf("Submodule status changed (-want +got):\n%s", diff)
+	}
+}
+
+func TestDeleteSubmodules(t *testing.T) {
+	subPath := "sub1"
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	diff := []DiffSubmodule{{
+		Path: subPath,
+	}}
+	if err := deleteSubmodules(gitSuperproject, diff); err != nil {
+		t.Fatalf("Failed to delete submodules: %s", err)
+	}
+	gitSubmodules := getSubmodules(t, gitSuperproject, false)
+	if _, ok := gitSubmodules[Key(subPath)]; ok {
+		t.Errorf("Submodule %s still present, expected it to be removed", subPath)
+	}
+}
+
+func TestDeleteSubmodulesEmpty(t *testing.T) {
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	want := getSubmodules(t, gitSuperproject, false)
+	diff := Diff{}
+	if err := deleteSubmodules(gitSuperproject, diff.DeletedSubmodules); err != nil {
+		t.Fatalf("Failed to delete submodules: %s", err)
+	}
+	got := getSubmodules(t, gitSuperproject, false)
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Errorf("Submodule status changed (-want +got):\n%s", diff)
+	}
+}
+
+func TestAddSubmodules(t *testing.T) {
+	subPath := "sub3"
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	testdataTemp, err := createTestdataTemp(t)
+	if err != nil {
+		t.Fatalf("Failed to create temp testdata dir: %s", err)
+	}
+	remotePath := filepath.Join(testdataTemp, "testdata", "remote", "sub3")
+	diff := []DiffSubmodule{{
+		Path:     subPath,
+		Remote:   remotePath,
+		Revision: "b6c817cd8a01b209f3b4f89cb55816f4173bbf4e",
+	}}
+	if err := addSubmodules(gitSuperproject, diff); err != nil {
+		t.Fatalf("Failed to add submodules: %s", err)
+	}
+	gitSubmodules := getSubmodules(t, gitSuperproject, false)
+	if _, ok := gitSubmodules[Key(subPath)]; !ok {
+		t.Errorf("Submodule %s not present, expected it to be added", subPath)
+	}
+}
+
+func TestAddSubmodulesEmpty(t *testing.T) {
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	want := getSubmodules(t, gitSuperproject, false)
+	diff := Diff{}
+	if err := addSubmodules(gitSuperproject, diff.NewSubmodules); err != nil {
+		t.Fatalf("Failed to add submodules: %s", err)
+	}
+	got := getSubmodules(t, gitSuperproject, false)
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Errorf("Submodule status changed (-want +got):\n%s", diff)
+	}
+}
+
+func TestCopyFile(t *testing.T) {
+	srcPath := "../testdata/ensure/cipd.ensure"
+	tempDir := t.TempDir()
+	dstPath := filepath.Join(tempDir, "copytest")
+
+	// Test new file creation
+	if err := copyFile(srcPath, dstPath); err != nil {
+		t.Fatalf("Failed to copy file: %s", err)
+	}
+	srcData, err := os.ReadFile(srcPath)
+	if err != nil {
+		t.Fatalf("Failed to read source file: %s", err)
+	}
+	dstData, err := os.ReadFile(dstPath)
+	if err != nil {
+		t.Fatalf("Failed to read destination file: %s", err)
+	}
+	if diff := cmp.Diff(string(srcData), string(dstData)); diff != "" {
+		t.Errorf("CopyFile to new file failed (-want +got):\n%s", diff)
+	}
+
+	// Test truncate/overwrite existing file
+	srcPath = "../testdata/ensure/cipd_internal.ensure"
+	if err = copyFile(srcPath, dstPath); err != nil {
+		t.Fatalf("Failed to copy file: %s", err)
+	}
+	srcData, err = os.ReadFile(srcPath)
+	if err != nil {
+		t.Fatalf("Failed to read source file: %s", err)
+	}
+	dstData, err = os.ReadFile(dstPath)
+	if err != nil {
+		t.Fatalf("Failed to read destination file: %s", err)
+	}
+	if diff := cmp.Diff(string(srcData), string(dstData)); diff != "" {
+		t.Errorf("CopyFile overwrite file failed (-want +got):\n%s", diff)
+	}
+}
+
+func TestCopyCIPDEnsure(t *testing.T) {
+	ensurePath := "../testdata/ensure/"
+	cipdPaths := map[string]string{
+		"ensure":          filepath.Join(ensurePath, "cipd.ensure"),
+		"internalEnsure":  filepath.Join(ensurePath, "cipd_internal.ensure"),
+		"internalVersion": filepath.Join(ensurePath, "cipd_internal.version"),
+		"version":         filepath.Join(ensurePath, "cipd.version"),
+	}
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	cipdPath := filepath.Join(gitSuperproject.RootDir(), "tools", "superproject")
+	err = copyCIPDEnsureToSuperproject(cipdPaths, cipdPath)
+	if err != nil {
+		t.Fatalf("Failed to copy ensure files: %s", err)
+	}
+	ensureFileList := []string{filepath.Join(cipdPath, path.Base(cipdPaths["ensure"])), filepath.Join(cipdPath, path.Base(cipdPaths["internalEnsure"]))}
+	versionFileList := []string{filepath.Join(cipdPath, path.Base(cipdPaths["version"])), filepath.Join(cipdPath, path.Base(cipdPaths["internalVersion"]))}
+	for _, dstPath := range ensureFileList {
+		dstDataBytes, err := os.ReadFile(dstPath)
+		if err != nil {
+			t.Fatalf("Failed to read destination file: %s", err)
+		}
+		dstData := string(dstDataBytes)
+		if !strings.HasSuffix(dstData, "Ensure updated\n") {
+			t.Fatalf("Ensure file not updated.")
+		}
+	}
+	for _, dstPath := range versionFileList {
+		dstDataBytes, err := os.ReadFile(dstPath)
+		if err != nil {
+			t.Fatalf("Failed to read destination file: %s", err)
+		}
+		dstData := string(dstDataBytes)
+		if !strings.HasSuffix(dstData, "Version updated\n") {
+			t.Fatalf("Version file not updated.")
+		}
+	}
+
+}
+
+// TODO(olivernewman): Re-enable these tests (or find another way to provide test-coverage)
+// This would require generating a jiri projects input file to match the remotes to the superproject temp directory
+// because remotes are considered in determining new/deleted/updated submodules.
+
+/*
+func TestUpdateSuperprojectRunsNoRecurse(t *testing.T) {
+	gitSuperproject, err := createSuperproject(t, false)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	jiriProjectsPath := "../testdata/jiri_projects_public.json"
+	outputPath := t.TempDir()
+	outputJSONPath := filepath.Join(outputPath, "json_output.json")
+	message := "Commit Message"
+	UpdateSuperproject(gitSuperproject, message, jiriProjectsPath, outputJSONPath)
+}
+
+func TestUpdateSuperprojectRunsWithRecurse(t *testing.T) {
+	gitSuperproject, err := createSuperproject(t, true)
+	if err != nil {
+		t.Fatalf("Failed to create superproject: %s", err)
+	}
+	jiriProjectsPath := "../testdata/jiri_projects_public.json"
+	outputPath := t.TempDir()
+	outputJSONPath := filepath.Join(outputPath, "json_output.json")
+	message := "Commit Message"
+	UpdateSuperproject(gitSuperproject, message, jiriProjectsPath, outputJSONPath)
+}
+*/
diff --git a/cmd/submodule_update/testdata/jiri_projects_public.json b/cmd/submodule_update/testdata/jiri_projects_public.json
index 3ff776a..167a0be 100644
--- a/cmd/submodule_update/testdata/jiri_projects_public.json
+++ b/cmd/submodule_update/testdata/jiri_projects_public.json
@@ -5,7 +5,8 @@
         "relativePath": "sub1",
         "remote": "sub1_remote",
         "revision": "4c473777b21946176bc7d157d195a29c108da6c6",
-        "manifest": "/usr/local/google/home/iankaz/fuchsia/.jiri_root/update_history/latest"
+        "manifest": "/usr/local/google/home/me/fuchsia/.jiri_root/update_history/latest",
+        "gitsubmoduleof": "fuchsia"
     },
     {
         "name": "sub2",
@@ -13,25 +14,26 @@
         "relativePath": "sub2",
         "remote": "sub2_remote",
         "revision": "cfb431368e1510b1a3f7cfdd70f8ce14aeb3d6e3",
-        "manifest": "/usr/local/google/home/iankaz/fuchsia/.jiri_root/update_history/latest"
+        "manifest": "/usr/local/google/home/me/fuchsia/.jiri_root/update_history/latest",
+        "gitsubmoduleof": "fuchsia"
     },
     {
         "name": "fuchsia",
-        "path": "/usr/local/google/home/iankaz/fuchsia",
+        "path": "/usr/local/google/home/me/fuchsia",
         "relativePath": ".",
         "remote": "https://fuchsia.googlesource.com/fuchsia",
         "revision": "052baf0f73298e11a37a1fb54bcdc4b170f65103",
-        "manifest": "/usr/local/google/home/iankaz/fuchsia/.jiri_root/update_history/latest"
+        "manifest": "/usr/local/google/home/me/fuchsia/.jiri_root/update_history/latest"
     },
     {
         "name": "integration",
-        "path": "/usr/local/google/home/iankaz/fuchsia/integration",
+        "path": "/usr/local/google/home/me/fuchsia/integration",
         "relativePath": "integration",
         "remote": "https://fuchsia.googlesource.com/integration",
         "revision": "cc90a7a07c55f303536759074c71d32ca66c43e0",
         "branches": [
             "main"
         ],
-        "manifest": "/usr/local/google/home/iankaz/fuchsia/.jiri_root/update_history/latest"
+        "manifest": "/usr/local/google/home/me/fuchsia/.jiri_root/update_history/latest"
     }
 ]