Merge "veyron/services/wspr: Setting NoTimeout for WSPR RPC calls. But we need to provide an API in JS for developers to set proper timeout. See https://code.google.com/p/envyor/issues/detail?id=48 for details."
diff --git a/services/store/memstore/blackbox/team_player_test.go b/services/store/memstore/blackbox/team_player_test.go
index fc9c3d0..61a1255 100644
--- a/services/store/memstore/blackbox/team_player_test.go
+++ b/services/store/memstore/blackbox/team_player_test.go
@@ -122,7 +122,9 @@
 	// Iterate over the rockets.
 	players := make(map[storage.ID]*Player)
 	name := storage.ParsePath("/teamsapp/players")
-	for it := st.Snapshot().NewIterator(rootPublicID, name, nil); it.IsValid(); it.Next() {
+	for it := st.Snapshot().NewIterator(rootPublicID, name,
+		state.ListPaths, nil); it.IsValid(); it.Next() {
+
 		e := it.Get()
 		if p, ok := e.Value.(*Player); ok {
 			if _, ok := players[e.Stat.ID]; ok {
@@ -148,7 +150,9 @@
 	// Iterate over all teams, nonrecursively.
 	teams := make(map[storage.ID]*Team)
 	name = storage.ParsePath("/teamsapp/teams")
-	for it := st.Snapshot().NewIterator(rootPublicID, name, state.ImmediateFilter); it.IsValid(); it.Next() {
+	for it := st.Snapshot().NewIterator(rootPublicID, name,
+		state.ListPaths, state.ImmediateFilter); it.IsValid(); it.Next() {
+
 		e := it.Get()
 		v := e.Value
 		if _, ok := v.(*Player); ok {
@@ -173,17 +177,19 @@
 	}
 
 	// Iterate over all teams, recursively.
-	playerCount := 0
+	contractCount := 0
 	teamCount := 0
 	players = make(map[storage.ID]*Player)
 	teams = make(map[storage.ID]*Team)
 	name = storage.ParsePath("/teamsapp/teams")
-	for it := st.Snapshot().NewIterator(rootPublicID, name, nil); it.IsValid(); it.Next() {
+	for it := st.Snapshot().NewIterator(rootPublicID, name,
+		state.ListPaths, nil); it.IsValid(); it.Next() {
+
 		e := it.Get()
 		v := e.Value
 		if p, ok := v.(*Player); ok {
 			players[e.Stat.ID] = p
-			playerCount++
+			contractCount++
 		}
 		if team, ok := v.(*Team); ok {
 			teams[e.Stat.ID] = team
@@ -202,8 +208,8 @@
 	if team, ok := teams[hornetsID]; !ok || team.FullName != "Hornets" {
 		t.Errorf("Should have Hornets, have %v", team)
 	}
-	if playerCount != 3 {
-		t.Errorf("Should have 3 players: have %d", playerCount)
+	if contractCount != 4 {
+		t.Errorf("Should have 4 contracts: have %d", contractCount)
 	}
 	if len(players) != 3 {
 		t.Errorf("Should have 3 players: have %v", players)
diff --git a/services/store/memstore/query/eval.go b/services/store/memstore/query/eval.go
index cd89615..48e991a 100644
--- a/services/store/memstore/query/eval.go
+++ b/services/store/memstore/query/eval.go
@@ -357,7 +357,9 @@
 		path := storage.ParsePath(naming.Join(c.suffix, basepath))
 		vlog.VI(2).Infof("nameEvaluator suffix: %s, result.Name: %s, VName: %s",
 			c.suffix, result.Name, e.wildcardName.VName)
-		for it := c.sn.NewIterator(c.clientID, path, state.ImmediateFilter); it.IsValid(); it.Next() {
+		for it := c.sn.NewIterator(c.clientID, path,
+			state.ListObjects, state.ImmediateFilter); it.IsValid(); it.Next() {
+
 			entry := it.Get()
 			result := &store.QueryResult{
 				Name:  naming.Join(basepath, it.Name()),
diff --git a/services/store/memstore/query/eval_test.go b/services/store/memstore/query/eval_test.go
index 282eaa0..2985265 100644
--- a/services/store/memstore/query/eval_test.go
+++ b/services/store/memstore/query/eval_test.go
@@ -492,7 +492,7 @@
 	it state.Iterator
 }
 
-func (m *mockSnapshot) NewIterator(pid security.PublicID, path storage.PathName, filter state.IterFilter) state.Iterator {
+func (m *mockSnapshot) NewIterator(pid security.PublicID, path storage.PathName, pathFilter state.PathFilter, filter state.IterFilter) state.Iterator {
 	return m.it
 }
 
diff --git a/services/store/memstore/query/glob.go b/services/store/memstore/query/glob.go
index 3614af3..8b1722e 100644
--- a/services/store/memstore/query/glob.go
+++ b/services/store/memstore/query/glob.go
@@ -32,7 +32,7 @@
 		pathLen: len(path),
 		glob:    parsed,
 	}
-	g.Iterator = sn.NewIterator(clientID, path, state.IterFilter(g.filter))
+	g.Iterator = sn.NewIterator(clientID, path, state.ListPaths, state.IterFilter(g.filter))
 
 	return g, nil
 }
diff --git a/services/store/memstore/query/glob_test.go b/services/store/memstore/query/glob_test.go
index 70ff580..6690d98 100644
--- a/services/store/memstore/query/glob_test.go
+++ b/services/store/memstore/query/glob_test.go
@@ -9,51 +9,64 @@
 	"veyron2/storage"
 )
 
-type nameOptions []string
-
 type globTest struct {
 	path     string
 	pattern  string
-	expected []nameOptions
+	expected []string
 }
 
 var globTests = []globTest{
-	{"", "mvps/...", []nameOptions{
-		{"mvps"},
-		{"mvps/Links/0"},
-		{"mvps/Links/1"},
+	{"", "...", []string{
+		"",
+		"mvps",
+		"mvps/Links/0",
+		"mvps/Links/1",
+		"players",
+		"players/alfred",
+		"players/alice",
+		"players/betty",
+		"players/bob",
+		"teams",
+		"teams/bears",
+		"teams/cardinals",
+		"teams/sharks",
 	}},
-	{"", "players/...", []nameOptions{
-		{"players"},
-		{"players/alfred"},
-		{"players/alice"},
-		{"players/betty"},
-		{"players/bob"},
+	{"", "mvps/...", []string{
+		"mvps",
+		"mvps/Links/0",
+		"mvps/Links/1",
+	}},
+	{"", "players/...", []string{
+		"players",
+		"players/alfred",
+		"players/alice",
+		"players/betty",
+		"players/bob",
 	}},
 	// Note(mattr): This test case shows that Glob does not return
 	// subfield nodes.
-	{"", "mvps/*", []nameOptions{}},
-	{"", "mvps/Links/*", []nameOptions{
-		{"mvps/Links/0"},
-		{"mvps/Links/1"},
+	{"", "mvps/*", []string{}},
+	{"", "mvps/Links/*", []string{
+		"mvps/Links/0",
+		"mvps/Links/1",
 	}},
-	{"", "players/alfred", []nameOptions{
-		{"players/alfred"},
+	{"", "players/alfred", []string{
+		"players/alfred",
 	}},
-	{"", "mvps/Links/0", []nameOptions{
-		{"mvps/Links/0"},
+	{"", "mvps/Links/0", []string{
+		"mvps/Links/0",
 	}},
 	// An empty pattern returns the element referred to by the path.
-	{"/mvps/Links/0", "", []nameOptions{
-		{""},
+	{"/mvps/Links/0", "", []string{
+		"",
 	}},
-	{"mvps", "Links/*", []nameOptions{
-		{"Links/0"},
-		{"Links/1"},
+	{"mvps", "Links/*", []string{
+		"Links/0",
+		"Links/1",
 	}},
-	{"mvps/Links", "*", []nameOptions{
-		{"0"},
-		{"1"},
+	{"mvps/Links", "*", []string{
+		"0",
+		"1",
 	}},
 }
 
@@ -102,16 +115,9 @@
 			t.Errorf("Wrong number of names for %s.  got %v, wanted %v",
 				gt.pattern, names, gt.expected)
 		}
-		for _, options := range gt.expected {
-			found := false
-			for _, name := range options {
-				if names[name] {
-					found = true
-					break
-				}
-			}
-			if !found {
-				t.Errorf("Expected to find one of %v in %v", options, names)
+		for _, name := range gt.expected {
+			if !names[name] {
+				t.Errorf("Expected to find %v in %v", name, names)
 			}
 		}
 	}
diff --git a/services/store/memstore/state/iterator.go b/services/store/memstore/state/iterator.go
index 92614be..70eac5c 100644
--- a/services/store/memstore/state/iterator.go
+++ b/services/store/memstore/state/iterator.go
@@ -37,10 +37,13 @@
 type iterator struct {
 	snapshot Snapshot
 
-	// Set of IDs already visited.
+	// Set of IDs already visited on this path.
 	visited map[storage.ID]struct{}
 
-	// Stack of IDs to visit next.  Some of these may already have been visited.
+	// Stack of actions to consider next. Actions are one of:
+	// - visit a node accessible from the current path (the node may already
+	//   have been visited on the current path).
+	// - unvisit a node (backtrack the current path).
 	next []next
 
 	// Depth of starting path.
@@ -50,6 +53,8 @@
 	entry *storage.Entry
 	path  *refs.FullPath
 
+	pathFilter PathFilter
+
 	filter IterFilter
 }
 
@@ -59,13 +64,32 @@
 	parent  *refs.FullPath
 	path    *refs.Path
 	id      storage.ID
+	action  action
 }
 
+type action int
+
+const (
+	visit = action(iota)
+	unvisit
+)
+
 var (
 	_ Iterator = (*iterator)(nil)
 	_ Iterator = (*errorIterator)(nil)
 )
 
+// A PathFilter automatically limits the traversal of certain paths,
+type PathFilter int
+
+const (
+	// ListPaths permits any path that does not visit the same object twice.
+	ListPaths = PathFilter(iota)
+	// ListObjects permits any path that does not revisit any object on a
+	// previously traversed path 'Q', even if Q did not satisfy it.filter.
+	ListObjects
+)
+
 // An IterFilter examines entries as they are considered by the
 // iterator and allows it to give two boolean inputs to the process:
 // ret: True if the iterator should return this value in its iteration.
@@ -84,11 +108,14 @@
 	return true, path == nil
 }
 
-// NewIterator returns an Iterator that starts with the value at
-// <path>.  If filter is given it is used to limit traversal beneath
-// certain paths. filter can be specified to limit the results of the iteration.
-// If filter is nil, all decendents of the specified path are returned.
-func (sn *snapshot) NewIterator(pid security.PublicID, path storage.PathName, filter IterFilter) Iterator {
+// NewIterator returns an Iterator that starts with the value at <path>.
+// pathFilter is used to automatically limit traversal of certain paths.
+// If filter is given, it is used to limit traversal beneath certain paths and
+// limit the results of the iteration. If filter is nil, all decendents of the
+// specified path are returned.
+func (sn *snapshot) NewIterator(pid security.PublicID, path storage.PathName,
+	pathFilter PathFilter, filter IterFilter) Iterator {
+
 	checker := sn.newPermChecker(pid)
 	cell, suffix, v := sn.resolveCell(checker, path, nil)
 	if cell == nil {
@@ -104,6 +131,7 @@
 		visited:      make(map[storage.ID]struct{}),
 		initialDepth: len(path),
 		path:         refs.NewFullPathFromName(path),
+		pathFilter:   pathFilter,
 		filter:       filter,
 	}
 
@@ -120,11 +148,12 @@
 	} else {
 		it.entry = cell.GetEntry()
 		it.visited[cell.ID] = struct{}{}
+		it.pushUnvisit(nil, cell.ID)
 		set = cell.refs
 	}
 
 	if expand {
-		it.pushAll(checker, it.path, set)
+		it.pushVisitAll(checker, it.path, set)
 	}
 	if !ret {
 		it.Next()
@@ -133,11 +162,25 @@
 	return it
 }
 
-func (it *iterator) pushAll(checker *acl.Checker, parentPath *refs.FullPath, set refs.Set) {
+func (it *iterator) pushUnvisit(path *refs.Path, id storage.ID) {
+	switch it.pathFilter {
+	case ListPaths:
+		it.next = append(it.next, next{nil, nil, path, id, unvisit})
+	case ListObjects:
+		// Do not unvisit the object, as it is on a path already seen by
+		// it.filter.
+	default:
+		panic("unknown PathFilter")
+	}
+}
+
+func (it *iterator) pushVisitAll(checker *acl.Checker,
+	parentPath *refs.FullPath, set refs.Set) {
+
 	set.Iter(func(x interface{}) bool {
 		ref := x.(*refs.Ref)
 		if checker.IsAllowed(ref.Label) {
-			it.next = append(it.next, next{checker, parentPath, ref.Path, ref.ID})
+			it.next = append(it.next, next{checker, parentPath, ref.Path, ref.ID, visit})
 		}
 		return true
 	})
@@ -171,10 +214,20 @@
 			return
 		}
 		n, it.next = it.next[topIndex], it.next[:topIndex]
+
+		if n.action == unvisit {
+			delete(it.visited, n.id)
+			continue
+		}
+
 		if _, ok := it.visited[n.id]; ok {
 			continue
 		}
 
+		// Mark as visited.
+		it.visited[n.id] = struct{}{}
+		it.pushUnvisit(n.path, n.id)
+
 		// Fetch the cell.
 		c = it.snapshot.Find(n.id)
 		if c == nil {
@@ -193,7 +246,7 @@
 		ret, expand := it.filter(n.parent, n.path)
 		fullPath = n.parent.AppendPath(n.path)
 		if expand {
-			it.pushAll(checker, fullPath, c.refs)
+			it.pushVisitAll(checker, fullPath, c.refs)
 		}
 		if ret {
 			// Found a value.
@@ -201,8 +254,6 @@
 		}
 	}
 
-	// Mark as visited.
-	it.visited[n.id] = struct{}{}
 	it.entry, it.path = c.GetEntry(), fullPath
 }
 
diff --git a/services/store/memstore/state/iterator_test.go b/services/store/memstore/state/iterator_test.go
index 344f69b..a9f3b2b 100644
--- a/services/store/memstore/state/iterator_test.go
+++ b/services/store/memstore/state/iterator_test.go
@@ -4,16 +4,48 @@
 	"runtime"
 	"testing"
 
+	"veyron/services/store/memstore/refs"
 	"veyron/services/store/memstore/state"
 	"veyron2/security"
 	"veyron2/storage"
 )
 
+// check that the iterator produces a set of names.
+func checkAcyclicIterator(t *testing.T, sn *state.MutableSnapshot, id security.PublicID, filter state.IterFilter, names []string) {
+	_, file, line, _ := runtime.Caller(1)
+
+	// Construct an index of names.
+	index := map[string]bool{}
+	for _, name := range names {
+		index[name] = false
+	}
+
+	// Compute the found names.
+	for it := sn.NewIterator(id, storage.ParsePath("/"), state.ListPaths, filter); it.IsValid(); it.Next() {
+		name := it.Name()
+		if found, ok := index[name]; ok {
+			if found {
+				t.Errorf("%s(%d): duplicate name %q", file, line, name)
+			}
+			index[name] = true
+		} else {
+			t.Errorf("%s(%d): unexpected name %q", file, line, name)
+		}
+	}
+
+	// Print the not found names.
+	for name, found := range index {
+		if !found {
+			t.Errorf("%s(%d): expected: %v", file, line, name)
+		}
+	}
+}
+
 // check that the iterator produces a set of names.  Since entries in the store
 // can have multiple names, the names are provided using a set of equivalence
 // classes.  The requirement is that the iterator produces exactly one name from
-// each equivalence class.  Order doesn't matter.
-func checkIterator(t *testing.T, sn *state.MutableSnapshot, id security.PublicID, names [][]string) {
+// each equivalence class. Order doesn't matter.
+func checkUniqueObjectsIterator(t *testing.T, sn *state.MutableSnapshot, id security.PublicID, filter state.IterFilter, names [][]string) {
 	_, file, line, _ := runtime.Caller(1)
 
 	// Construct an index of name to equivalence class.
@@ -26,7 +58,7 @@
 
 	// Compute the found set of equivalence classes.
 	found := map[int]bool{}
-	for it := sn.NewIterator(id, storage.ParsePath("/"), nil); it.IsValid(); it.Next() {
+	for it := sn.NewIterator(id, storage.ParsePath("/"), state.ListObjects, filter); it.IsValid(); it.Next() {
 		name := it.Name()
 		if i, ok := index[name]; ok {
 			if _, ok := found[i]; ok {
@@ -46,6 +78,55 @@
 	}
 }
 
+// Tests that an iterator returns all non-cyclic paths that reach an object.
+func TestDuplicatePaths(t *testing.T) {
+	st := state.New(rootPublicID)
+	sn := st.MutableSnapshot()
+
+	// Add some objects
+	put(t, sn, rootPublicID, "/", "")
+	put(t, sn, rootPublicID, "/teams", "")
+	put(t, sn, rootPublicID, "/teams/cardinals", "")
+	put(t, sn, rootPublicID, "/players", "")
+	mattID := put(t, sn, rootPublicID, "/players/matt", "")
+
+	// Add some hard links
+	put(t, sn, rootPublicID, "/teams/cardinals/mvp", mattID)
+
+	checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
+		"",
+		"teams",
+		"players",
+		"teams/cardinals",
+		"players/matt",
+		"teams/cardinals/mvp",
+	})
+	checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
+		{""},
+		{"teams"},
+		{"players"},
+		{"teams/cardinals"},
+		{"players/matt", "teams/cardinals/mvp"},
+	})
+
+	// Test that the iterator does not revisit objects on previously rejected paths.
+	rejected := false
+	rejectMatt := func(fullPath *refs.FullPath, path *refs.Path) (bool, bool) {
+		name := fullPath.Append(path.Suffix(1)).Name().String()
+		if !rejected && (name == "players/matt" || name == "teams/cardinals/mvp") {
+			rejected = true
+			return false, true
+		}
+		return true, true
+	}
+	checkUniqueObjectsIterator(t, sn, rootPublicID, rejectMatt, [][]string{
+		{""},
+		{"teams"},
+		{"players"},
+		{"teams/cardinals"},
+	})
+}
+
 // Test that an iterator doesn't get stuck in cycles.
 func TestCyclicStructure(t *testing.T) {
 	st := state.New(rootPublicID)
@@ -63,7 +144,17 @@
 	put(t, sn, rootPublicID, "/players/matt/team", cardinalsID)
 	put(t, sn, rootPublicID, "/teams/cardinals/mvp", mattID)
 
-	checkIterator(t, sn, rootPublicID, [][]string{
+	checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
+		"",
+		"teams",
+		"players",
+		"players/joe",
+		"players/matt",
+		"teams/cardinals/mvp",
+		"teams/cardinals",
+		"players/matt/team",
+	})
+	checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
 		{""},
 		{"teams"},
 		{"players"},
@@ -108,7 +199,21 @@
 	put(t, sn, rootPublicID, "/Users/john/shared", sharedID)
 
 	// Root gets everything.
-	checkIterator(t, sn, rootPublicID, [][]string{
+	checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
+		"",
+		"Users",
+		"Users/jane",
+		"Users/jane/acls",
+		"Users/jane/acls/janeRWA",
+		"Users/jane/aaa",
+		"Users/john",
+		"Users/john/acls",
+		"Users/john/acls/johnRWA",
+		"Users/john/aaa",
+		"Users/jane/shared",
+		"Users/john/shared",
+	})
+	checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
 		{""},
 		{"Users"},
 		{"Users/jane"},
@@ -123,7 +228,16 @@
 	})
 
 	// Jane sees only her names.
-	checkIterator(t, sn, janePublicID, [][]string{
+	checkAcyclicIterator(t, sn, janePublicID, nil, []string{
+		"",
+		"Users",
+		"Users/jane",
+		"Users/jane/acls",
+		"Users/jane/acls/janeRWA",
+		"Users/jane/aaa",
+		"Users/jane/shared",
+	})
+	checkUniqueObjectsIterator(t, sn, janePublicID, nil, [][]string{
 		{""},
 		{"Users"},
 		{"Users/jane"},
@@ -134,7 +248,16 @@
 	})
 
 	// John sees only his names.
-	checkIterator(t, sn, johnPublicID, [][]string{
+	checkAcyclicIterator(t, sn, johnPublicID, nil, []string{
+		"",
+		"Users",
+		"Users/john",
+		"Users/john/acls",
+		"Users/john/acls/johnRWA",
+		"Users/john/aaa",
+		"Users/john/shared",
+	})
+	checkUniqueObjectsIterator(t, sn, johnPublicID, nil, [][]string{
 		{""},
 		{"Users"},
 		{"Users/john"},
diff --git a/services/store/memstore/state/snapshot.go b/services/store/memstore/state/snapshot.go
index fd3a032..4213a55 100644
--- a/services/store/memstore/state/snapshot.go
+++ b/services/store/memstore/state/snapshot.go
@@ -12,11 +12,12 @@
 )
 
 type Snapshot interface {
-	// NewIterator returns an Iterator that starts with the value at <path>.  If
-	// filter is given it is used to limit traversal beneath certain paths.
-	// filter can be specified to limit the results of the iteration. If filter
-	// is nil, all decendents of the specified path are returned.
-	NewIterator(pid security.PublicID, path storage.PathName, filter IterFilter) Iterator
+	// NewIterator returns an Iterator that starts with the value at <path>.
+	// pathFilter is used to automatically limit traversal of certain paths.
+	// If filter is given, it is used to limit traversal beneath certain paths
+	// and limit the results of the iteration. If filter is nil, all decendents
+	// of the specified path are returned.
+	NewIterator(pid security.PublicID, path storage.PathName, pathFilter PathFilter, filter IterFilter) Iterator
 
 	// PathMatch returns true iff there is a name for the store value that
 	// matches the pathRegex.
diff --git a/services/store/memstore/watch/glob_processor_test.go b/services/store/memstore/watch/glob_processor_test.go
index 699d5f5..02f73c7 100644
--- a/services/store/memstore/watch/glob_processor_test.go
+++ b/services/store/memstore/watch/glob_processor_test.go
@@ -19,6 +19,9 @@
 	id1 := put(t, st, tr, "/", "val1")
 	id2 := put(t, st, tr, "/a", "val2")
 	put(t, st, tr, "/a/b", "val3")
+	id4 := put(t, st, tr, "/a/c", "val4")
+	// Test duplicate paths to the same object.
+	put(t, st, tr, "/a/d", id4)
 	commit(t, tr)
 
 	// Remove /a/b.
@@ -44,20 +47,26 @@
 	aRecursiveProcessor := createGlobProcessor(t, storage.ParsePath("/a"), "...")
 	aListProcessor := createGlobProcessor(t, storage.ParsePath("/a"), "*")
 
-	// Expect initial state that contains / and /a.
+	// Expect initial state that contains /, /a, /a/c and /a/d.
 	logst := readState(t, log)
 
-	changes := processState(t, rootRecursiveProcessor, logst, 2)
+	changes := processState(t, rootRecursiveProcessor, logst, 4)
 	watchtesting.ExpectEntryExists(t, changes, "", id1, "val1")
 	watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
+	watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+	watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
 
 	changes = processState(t, rootListProcessor, logst, 1)
 	watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
 
-	changes = processState(t, aRecursiveProcessor, logst, 1)
+	changes = processState(t, aRecursiveProcessor, logst, 3)
 	watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
+	watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+	watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
 
-	processState(t, aListProcessor, logst, 0)
+	processState(t, aListProcessor, logst, 2)
+	watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+	watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
 }
 
 func TestGlobProcessTransactionAdd(t *testing.T) {
@@ -85,25 +94,34 @@
 	id1 := put(t, st, tr, "/", "val1")
 	id2 := put(t, st, tr, "/a", "val2")
 	id3 := put(t, st, tr, "/a/b", "val3")
+	id4 := put(t, st, tr, "/a/c", "val4")
+	// Test duplicate paths to the same object.
+	put(t, st, tr, "/a/d", id4)
 	commit(t, tr)
 
 	// Expect transaction that adds /, /a and /a/b.
 	mus := readTransaction(t, log)
 
-	changes := processTransaction(t, rootRecursiveProcessor, mus, 3)
+	changes := processTransaction(t, rootRecursiveProcessor, mus, 5)
 	watchtesting.ExpectEntryExists(t, changes, "", id1, "val1")
 	watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
 	watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
+	watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+	watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
 
 	changes = processTransaction(t, rootListProcessor, mus, 1)
 	watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
 
-	changes = processTransaction(t, aRecursiveProcessor, mus, 2)
+	changes = processTransaction(t, aRecursiveProcessor, mus, 4)
 	watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
 	watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
+	watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+	watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
 
-	changes = processTransaction(t, aListProcessor, mus, 1)
+	changes = processTransaction(t, aListProcessor, mus, 3)
 	watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
+	watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+	watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
 
 	changes = processTransaction(t, bRecursiveProcessor, mus, 1)
 	watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
diff --git a/services/store/memstore/watch/raw_processor.go b/services/store/memstore/watch/raw_processor.go
index a783caf..5c8d76e 100644
--- a/services/store/memstore/watch/raw_processor.go
+++ b/services/store/memstore/watch/raw_processor.go
@@ -67,7 +67,9 @@
 	// Create a change for each id in the state. In each change, the object
 	// exists, has no PriorVersion, has the Version of the new cell, and
 	// has the Value, Tags and Dir of the new cell.
-	for it := sn.NewIterator(p.pid, nil, state.RecursiveFilter); it.IsValid(); it.Next() {
+	for it := sn.NewIterator(p.pid, nil,
+		state.ListObjects, state.RecursiveFilter); it.IsValid(); it.Next() {
+
 		entry := it.Get()
 		id := entry.Stat.ID
 		// Retrieve Value, Tags and Dir from the corresponding cell.