[submodule_update] Don't delete source-of-truth submodules
The `name` field in .gitmodules entries is generated by submodule_update
and is used to map the Jiri project name to the submodule. Therefore,
submodules that don't have corresponding Jiri projects need not have the
`name` field set. So we can use the presence/absence of the `name` field
to determine whether a submodule is the source of truth or whether it's
still being mirrored from Jiri, and don't attempt to delete such
submodules.
Bug: 326595281
Change-Id: Ib8a4a1a95d316380a2d5c2d36a9b40f6d1036331
Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/infra/+/997592
Reviewed-by: Danielle Kay <danikay@google.com>
Commit-Queue: Oliver Newman <olivernewman@google.com>
diff --git a/cmd/submodule_update/gitutil/git.go b/cmd/submodule_update/gitutil/git.go
index 09916c8..37eb92a 100644
--- a/cmd/submodule_update/gitutil/git.go
+++ b/cmd/submodule_update/gitutil/git.go
@@ -421,13 +421,18 @@
}
// ConfigGetKeyFromFile gets current git configuration value for the given key from file.
+//
+// Returns an empty string if the configuration value is not found.
func (g *Git) ConfigGetKeyFromFile(key string, file string) (string, error) {
- out, err := g.runOutput("config", "--file", file, "--get", key)
+ out, err := g.runOutput("config", "--file", file, "--default", "", "--get", key)
if err != nil {
return "", err
}
- if got, want := len(out), 1; got != want {
- glog.Warning("wanted one line log, got %d line log: %q", got, out)
+ if len(out) == 0 {
+ return "", nil
+ }
+ if len(out) > 1 {
+ glog.Warning("wanted one line log, got %d line log: %q", len(out), out)
}
return out[0], nil
}
diff --git a/cmd/submodule_update/submodule/submodule.go b/cmd/submodule_update/submodule/submodule.go
index 0887a72..8d55510 100644
--- a/cmd/submodule_update/submodule/submodule.go
+++ b/cmd/submodule_update/submodule/submodule.go
@@ -42,19 +42,17 @@
return Key(s.Path)
}
-var /* const */ submoduleStatusRe = regexp.MustCompile(`(?m)^[+\-U\s]?([0-9a-f]{40})\s([a-zA-Z0-9_.\-\/]+).*$`)
+var submoduleStatusRe = regexp.MustCompile(`(?m)^[+\-U\s]?([0-9a-f]{40})\s([a-zA-Z0-9_.\-\/]+).*$`)
func gitSubmodules(g *gitutil.Git, cached bool) (Submodules, error) {
gitSubmoduleStatus, err := g.SubmoduleStatus(gitutil.CachedOpt(cached))
if err != nil {
return nil, err
}
- gitSubmodules := gitSubmoduleStatusToSubmodule(gitSubmoduleStatus)
- addRemoteToSubmodules(g, gitSubmodules)
- return gitSubmodules, nil
+ return gitSubmoduleStatusToSubmodule(g, gitSubmoduleStatus)
}
-func gitSubmoduleStatusToSubmodule(status string) Submodules {
+func gitSubmoduleStatusToSubmodule(g *gitutil.Git, status string) (Submodules, error) {
var subModules = Submodules{}
subStatus := submoduleStatusRe.FindAllStringSubmatch(status, -1)
for _, status := range subStatus {
@@ -66,22 +64,23 @@
Path: status[2],
Revision: status[1],
}
- subModules[subM.Key()] = subM
- }
- return subModules
-}
-
-func addRemoteToSubmodules(g *gitutil.Git, s Submodules) error {
- for _, subM := range s {
- configKey := fmt.Sprintf("submodule.%s.url", subM.Path)
- url, err := g.ConfigGetKeyFromFile(configKey, ".gitmodules")
+ url, err := g.ConfigGetKeyFromFile(
+ fmt.Sprintf("submodule.%s.url", subM.Path),
+ ".gitmodules")
if err != nil {
- return err
+ return nil, err
}
subM.Remote = url
- s[Key(subM.Path)] = subM
+ name, err := g.ConfigGetKeyFromFile(
+ fmt.Sprintf("submodule.%s.name", subM.Path),
+ ".gitmodules")
+ if err != nil {
+ return nil, err
+ }
+ subM.Name = name
+ subModules[subM.Key()] = subM
}
- return nil
+ return subModules, nil
}
func addIgnoreToSubmodulesConfig(g *gitutil.Git, s Submodule) error {
@@ -296,11 +295,18 @@
return nil
}
-func getDiff(submodules1, submodules2 Submodules) (Diff, error) {
+func getDiff(gitSubmodules, jiriSubmodules Submodules) (Diff, error) {
diff := Diff{}
// Get deleted submodules
- for key, s1 := range submodules1 {
- if _, ok := submodules2[key]; !ok {
+ for key, s1 := range gitSubmodules {
+ if s1.Name == "" {
+ // Submodules that are source-of-truth in the superproject will not
+ // have the `name` field set. It is only set for submodules that
+ // correspond to Jiri projects. Submodules that intentionally do not
+ // correspond to any Jiri project should not be deleted.
+ continue
+ }
+ if _, ok := jiriSubmodules[key]; !ok {
diff.DeletedSubmodules = append(diff.DeletedSubmodules, DiffSubmodule{
Path: s1.Path,
Revision: s1.Revision,
@@ -310,8 +316,8 @@
}
// Get new and updated submodules
- for key, s2 := range submodules2 {
- if s1, ok := submodules1[key]; !ok {
+ for key, s2 := range jiriSubmodules {
+ if s1, ok := gitSubmodules[key]; !ok {
diff.NewSubmodules = append(diff.NewSubmodules, DiffSubmodule{
Name: s2.Name,
Path: s2.Path,
diff --git a/cmd/submodule_update/submodule/submodule_test.go b/cmd/submodule_update/submodule/submodule_test.go
index 6acc022..ed949f8 100644
--- a/cmd/submodule_update/submodule/submodule_test.go
+++ b/cmd/submodule_update/submodule/submodule_test.go
@@ -69,6 +69,7 @@
var subModules = Submodules{}
for subPath, rev := range submodules {
subM := Submodule{
+ Name: subPath,
Path: subPath,
Revision: rev,
// For testing, fake the remote
@@ -96,60 +97,93 @@
remoteSub1.Remote = path.Join("new", "remote", "sub1")
remoteChangeSub[Key("sub1")] = remoteSub1
- emptyDiff := Diff{}
type diffTest struct {
- sub1 Submodules
- sub2 Submodules
- want Diff
+ name string
+ gitSubmodules Submodules
+ jiriSubmodules Submodules
+ want Diff
}
testCases := []diffTest{
- // No diff
{
- initialSub,
- initialSub,
- emptyDiff,
+ name: "no diff",
+ gitSubmodules: initialSub,
+ jiriSubmodules: initialSub,
+ want: Diff{},
},
- // Add sub3
{
- initialSub,
- createSubmodules(t, map[string]string{"sub1": "12345a", "sub2": "12345b", "sub3": "12345c"}),
- Diff{
+ name: "add sub3",
+ gitSubmodules: initialSub,
+ jiriSubmodules: createSubmodules(t, map[string]string{
+ "sub1": "12345a",
+ "sub2": "12345b",
+ "sub3": "12345c",
+ }),
+ want: Diff{
NewSubmodules: []DiffSubmodule{{
+ Name: "sub3",
Path: "sub3",
Revision: "12345c",
Remote: path.Join("remote", "sub3"),
}},
},
},
- // Delete sub1
{
- initialSub,
- createSubmodules(t, map[string]string{"sub2": "12345b"}),
- Diff{
+ name: "delete sub1",
+ gitSubmodules: initialSub,
+ jiriSubmodules: createSubmodules(t, map[string]string{
+ "sub2": "12345b",
+ }),
+ want: 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{
+ name: "don't delete nameless submodule",
+ gitSubmodules: func() Submodules {
+ submodules := createSubmodules(t, map[string]string{
+ "sub1": "12345a",
+ "nameless": "12345b",
+ })
+ subm := submodules[Key("nameless")]
+ subm.Name = ""
+ submodules[Key("nameless")] = subm
+ return submodules
+ }(),
+ jiriSubmodules: createSubmodules(t, map[string]string{
+ "sub1": "12345a",
+ }),
+ want: Diff{},
+ },
+ {
+ name: "update sub1 (new revision)",
+ gitSubmodules: initialSub,
+ jiriSubmodules: createSubmodules(t, map[string]string{
+ "sub1": "12346a",
+ "sub2": "12345b",
+ }),
+ want: Diff{
UpdatedSubmodules: []DiffSubmodule{{
+ Name: "sub1",
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{
+ // Processed as a delete (old path) and a new submodule (new path).
+ name: "move sub1 (change path)",
+ gitSubmodules: initialSub,
+ jiriSubmodules: createSubmodules(t, map[string]string{
+ "sub3": "12345a",
+ "sub2": "12345b",
+ }),
+ want: Diff{
NewSubmodules: []DiffSubmodule{{
+ Name: "sub3",
Path: "sub3",
Revision: "12345a",
Remote: path.Join("remote", "sub3")}},
@@ -159,12 +193,14 @@
Remote: path.Join("remote", "sub1")}},
},
},
- // Move sub1 (change remote) - Processed as a delete (old remote) and a new submodule (new remote)
{
- initialSub,
- remoteChangeSub,
- Diff{
+ // Processed as a delete (old remote) and a new submodule (new remote)
+ name: "move sub1 (change remote)",
+ gitSubmodules: initialSub,
+ jiriSubmodules: remoteChangeSub,
+ want: Diff{
NewSubmodules: []DiffSubmodule{{
+ Name: "sub1",
Path: "sub1",
Revision: "12345a",
Remote: path.Join("new", "remote", "sub1")}},
@@ -177,14 +213,16 @@
}
for _, tc := range testCases {
- got, err := getDiff(tc.sub1, tc.sub2)
- if err != nil {
- t.Fatalf("GetDiff failed with: %s", err)
- }
+ t.Run(tc.name, func(t *testing.T) {
+ got, err := getDiff(tc.gitSubmodules, tc.jiriSubmodules)
+ 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)
- }
+ if diff := cmp.Diff(tc.want, got); diff != "" {
+ t.Errorf("GetDiff failed (-want +got):\n%s", diff)
+ }
+ })
}
}