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 {