Handle DoNotMergeItems in hierarchy merge (#1922)
## Description The DoNotMergeItems flag denotes situations where we want to propagate changes to the hierarchy but do not want to source items from the base for a specific directory. As of now, the only time we expect to encounter this situation is when a delta token expires in M365 and we need to pull all the items for the container again. By setting DoNotMergeItems, a collection can propagate things like rename to its subtree while avoiding zombie items that would have appeared if there was a deletion in the container and the container was enumerated again ## Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [x] ⛔ No ## Type of change - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Test - [ ] 💻 CI/Deployment - [ ] 🐹 Trivial/Minor ## Issue(s) * #1914 ## Test Plan - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
bc56d38970
commit
c3dbd5e0a8
@ -759,6 +759,15 @@ func traverseBaseDir(
|
|||||||
return errors.Errorf("unable to get tree node for path %s", currentPath)
|
return errors.Errorf("unable to get tree node for path %s", currentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that we have the node we need to check if there is a collection
|
||||||
|
// marked DoNotMerge. If there is, skip adding a reference to this base dir
|
||||||
|
// in the node. That allows us to propagate subtree operations (e.x. move)
|
||||||
|
// while selectively skipping merging old and new versions for some
|
||||||
|
// directories. The expected usecase for this is delta token expiry in M365.
|
||||||
|
if node.collection != nil && node.collection.DoNotMergeItems() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
curP, err := path.FromDataLayerPath(currentPath.String(), false)
|
curP, err := path.FromDataLayerPath(currentPath.String(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
|
|||||||
@ -1189,6 +1189,10 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
[]string{testTenant, service, testUser, category, testInboxDir},
|
[]string{testTenant, service, testUser, category, testInboxDir},
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
inboxFileName1 := testFileName4
|
||||||
|
inboxFileData1 := testFileData4
|
||||||
|
inboxFileName2 := testFileName5
|
||||||
|
inboxFileData2 := testFileData5
|
||||||
|
|
||||||
personalPath := makePath(
|
personalPath := makePath(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
@ -1213,6 +1217,7 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
// - user1
|
// - user1
|
||||||
// - email
|
// - email
|
||||||
// - Inbox
|
// - Inbox
|
||||||
|
// - file4
|
||||||
// - personal
|
// - personal
|
||||||
// - file1
|
// - file1
|
||||||
// - file2
|
// - file2
|
||||||
@ -1230,6 +1235,11 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
virtualfs.NewStaticDirectory(
|
virtualfs.NewStaticDirectory(
|
||||||
encodeElements(testInboxDir)[0],
|
encodeElements(testInboxDir)[0],
|
||||||
[]fs.Entry{
|
[]fs.Entry{
|
||||||
|
virtualfs.StreamingFileWithModTimeFromReader(
|
||||||
|
encodeElements(inboxFileName1)[0],
|
||||||
|
time.Time{},
|
||||||
|
bytes.NewReader(inboxFileData1),
|
||||||
|
),
|
||||||
virtualfs.NewStaticDirectory(
|
virtualfs.NewStaticDirectory(
|
||||||
encodeElements(personalDir)[0],
|
encodeElements(personalDir)[0],
|
||||||
[]fs.Entry{
|
[]fs.Entry{
|
||||||
@ -1292,6 +1302,10 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
{
|
{
|
||||||
name: testInboxDir + "2",
|
name: testInboxDir + "2",
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: personalDir,
|
name: personalDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
@ -1354,6 +1368,10 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
{
|
{
|
||||||
name: testInboxDir + "2",
|
name: testInboxDir + "2",
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: personalDir,
|
name: personalDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
@ -1444,6 +1462,10 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
{
|
{
|
||||||
name: testInboxDir,
|
name: testInboxDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: personalDir,
|
name: personalDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
@ -1487,6 +1509,10 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
{
|
{
|
||||||
name: testInboxDir,
|
name: testInboxDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: personalDir,
|
name: personalDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
@ -1541,6 +1567,10 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
{
|
{
|
||||||
name: testInboxDir,
|
name: testInboxDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: workDir,
|
name: workDir,
|
||||||
children: []*expectedNode{
|
children: []*expectedNode{
|
||||||
@ -1571,6 +1601,157 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "MoveParentDeleteFileNoMergeSubtreeMerge",
|
||||||
|
inputCollections: func(t *testing.T) []data.Collection {
|
||||||
|
newInboxPath := makePath(
|
||||||
|
t,
|
||||||
|
[]string{testTenant, service, testUser, category, personalDir},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
// This path is implicitly updated because we update the inbox path. If
|
||||||
|
// we didn't update it here then it would end up at the old location
|
||||||
|
// still.
|
||||||
|
newWorkPath := makePath(
|
||||||
|
t,
|
||||||
|
[]string{testTenant, service, testUser, category, personalDir, workDir},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
inbox := mockconnector.NewMockExchangeCollection(newInboxPath, 1)
|
||||||
|
inbox.PrevPath = inboxPath
|
||||||
|
inbox.ColState = data.MovedState
|
||||||
|
inbox.DoNotMerge = true
|
||||||
|
// First file in inbox is implicitly deleted as we're not merging items
|
||||||
|
// and it's not listed.
|
||||||
|
inbox.Names[0] = inboxFileName2
|
||||||
|
inbox.Data[0] = inboxFileData2
|
||||||
|
|
||||||
|
work := mockconnector.NewMockExchangeCollection(newWorkPath, 1)
|
||||||
|
work.PrevPath = workPath
|
||||||
|
work.ColState = data.MovedState
|
||||||
|
work.Names[0] = testFileName6
|
||||||
|
work.Data[0] = testFileData6
|
||||||
|
|
||||||
|
return []data.Collection{inbox, work}
|
||||||
|
},
|
||||||
|
expected: expectedTreeWithChildren(
|
||||||
|
[]string{
|
||||||
|
testTenant,
|
||||||
|
service,
|
||||||
|
testUser,
|
||||||
|
category,
|
||||||
|
},
|
||||||
|
[]*expectedNode{
|
||||||
|
{
|
||||||
|
name: personalDir,
|
||||||
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName2,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
data: inboxFileData2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: personalDir,
|
||||||
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: personalFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: personalFileName2,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: workDir,
|
||||||
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: workFileName,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: testFileName6,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
data: testFileData6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoMoveParentDeleteFileNoMergeSubtreeMerge",
|
||||||
|
inputCollections: func(t *testing.T) []data.Collection {
|
||||||
|
inbox := mockconnector.NewMockExchangeCollection(inboxPath, 1)
|
||||||
|
inbox.PrevPath = inboxPath
|
||||||
|
inbox.ColState = data.NotMovedState
|
||||||
|
inbox.DoNotMerge = true
|
||||||
|
// First file in inbox is implicitly deleted as we're not merging items
|
||||||
|
// and it's not listed.
|
||||||
|
inbox.Names[0] = inboxFileName2
|
||||||
|
inbox.Data[0] = inboxFileData2
|
||||||
|
|
||||||
|
work := mockconnector.NewMockExchangeCollection(workPath, 1)
|
||||||
|
work.PrevPath = workPath
|
||||||
|
work.ColState = data.NotMovedState
|
||||||
|
work.Names[0] = testFileName6
|
||||||
|
work.Data[0] = testFileData6
|
||||||
|
|
||||||
|
return []data.Collection{inbox, work}
|
||||||
|
},
|
||||||
|
expected: expectedTreeWithChildren(
|
||||||
|
[]string{
|
||||||
|
testTenant,
|
||||||
|
service,
|
||||||
|
testUser,
|
||||||
|
category,
|
||||||
|
},
|
||||||
|
[]*expectedNode{
|
||||||
|
{
|
||||||
|
name: testInboxDir,
|
||||||
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: inboxFileName2,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
data: inboxFileData2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: personalDir,
|
||||||
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: personalFileName1,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: personalFileName2,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: workDir,
|
||||||
|
children: []*expectedNode{
|
||||||
|
{
|
||||||
|
name: workFileName,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: testFileName6,
|
||||||
|
children: []*expectedNode{},
|
||||||
|
data: testFileData6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user