Handle links to multiple older workspaces. This can happen if a previous populate operation was interrupted. Also handle links to removed workspaces. Change-Id: I0c12a4ba5800f7a330f5a0ff4a0733eb8ea0bc9b
diff --git a/populate/e2e_test.go b/populate/e2e_test.go index 619e630..c57e78c 100644 --- a/populate/e2e_test.go +++ b/populate/e2e_test.go
@@ -186,6 +186,59 @@ } +func TestBrokenWorkspaceLink(t *testing.T) { + fixture, err := newFixture() + if err != nil { + t.Fatal(err) + } + defer fixture.Cleanup() + + // We avoid talking to gitiles by inserting entries into the + // cache manually. + if err := fixture.cache.Tree.Add(gitID(ids[0]), &gitiles.Tree{ + ID: ids[0], + Entries: []gitiles.TreeEntry{ + { + Mode: 0100644, + Name: "a", + Type: "blob", + ID: ids[1], + Size: newInt(42), + }, + }, + }); err != nil { + t.Fatal(err) + } + + for i := 0; i <= 1; i++ { + if err := fixture.addWorkspace(fmt.Sprintf("m%d", i), &manifest.Manifest{ + Project: []manifest.Project{{ + Name: "platform/project", + Path: "p", + Revision: ids[0], + }}}); err != nil { + t.Fatalf("addWorkspace(%d): %v", i, err) + } + } + + ws := filepath.Join(fixture.dir, "ws") + m0 := filepath.Join(fixture.dir, "mnt", "m0") + if _, _, err := Checkout(m0, ws); err != nil { + t.Fatalf("Checkout(m0): %v", err) + } + + if err := os.Remove(filepath.Join(fixture.dir, "mnt", "config", "m0")); err != nil { + log.Fatalf("Remove: %v", err) + } + + m1 := filepath.Join(fixture.dir, "mnt", "m1") + if _, changed, err := Checkout(m1, ws); err != nil { + t.Fatalf("Checkout(m1): %v", err) + } else if len(changed) > 0 { + t.Errorf("Got changed files %v relative to broken link.") + } +} + func TestFUSESymlink(t *testing.T) { fixture, err := newFixture() if err != nil {
diff --git a/populate/populate.go b/populate/populate.go index a8fd03b..3df38c1 100644 --- a/populate/populate.go +++ b/populate/populate.go
@@ -119,12 +119,13 @@ return nil } -// clearLinks removes all symlinks to the RO tree. It returns the workspace name that was linked before. -func clearLinks(mount, dir string) (string, error) { +// clearLinks removes all symlinks to the RO tree. It returns the workspace names that were linked before. +func clearLinks(mount, dir string) (map[string]struct{}, error) { mount = filepath.Clean(mount) - var prefix string var dirs []string + + prevPrefixes := map[string]struct{}{} if err := filepath.Walk(dir, func(n string, fi os.FileInfo, err error) error { if fi == nil { return fmt.Errorf("Walk %s: nil fileinfo for %s", dir, n) @@ -135,7 +136,7 @@ return err } if strings.HasPrefix(target, mount) { - prefix = target + prevPrefixes[trimMount(target, mount)] = struct{}{} if err := os.Remove(n); err != nil { return err } @@ -146,22 +147,27 @@ } return nil }); err != nil { - return "", fmt.Errorf("Walk %s: %v", dir, err) + return nil, fmt.Errorf("Walk %s: %v", dir, err) } - // Reverse the ordering, so we get the deepest subdirs first. sort.Strings(dirs) for i := range dirs { + // Reverse the ordering, so we get the deepest subdirs first. d := dirs[len(dirs)-1-i] // Ignore error: dir may still contain entries. os.Remove(d) } - prefix = strings.TrimPrefix(prefix, mount+"/") - if i := strings.Index(prefix, "/"); i != -1 { - prefix = prefix[:i] + return prevPrefixes, nil +} + +func trimMount(dir, mount string) string { + dir = strings.TrimPrefix(dir, mount+"/") + if i := strings.Index(dir, "/"); i != -1 { + dir = dir[:i] } - return prefix, nil + + return dir } // Returns the filenames (as relative paths) in newDir that have @@ -192,18 +198,26 @@ // Returns the files that should be touched. func Checkout(ro, rw string) (added, changed []string, err error) { ro = filepath.Clean(ro) - wsName, err := clearLinks(filepath.Dir(ro), rw) + wsNames, err := clearLinks(filepath.Dir(ro), rw) if err != nil { return nil, nil, err } - oldRoot := filepath.Join(filepath.Dir(ro), wsName) + + oldRoot := "" + for nm := range wsNames { + r := filepath.Join(filepath.Dir(ro), nm) + if _, err := os.Stat(r); err == nil && r != ro { + oldRoot = r + break + } + } // Do the file system traversals in parallel. errs := make(chan error, 3) var rwTree, roTree *repoTree var oldInfos map[string]*fileInfo - if wsName != "" { + if oldRoot != "" { go func() { t, err := repoTreeFromSlothFS(oldRoot) if t != nil {