github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/cr_actions.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "fmt" 9 10 "github.com/keybase/client/go/kbfs/data" 11 "github.com/keybase/client/go/kbfs/idutil" 12 "github.com/pkg/errors" 13 "golang.org/x/net/context" 14 ) 15 16 // copyUnmergedEntryAction says that the unmerged entry for the given 17 // name should be copied directly into the merged version of the 18 // directory; there should be no conflict. If symPath is non-empty, then 19 // the unmerged entry becomes a symlink to that path. 20 // 21 // Note that under some circumstances (e.g., when the target entry has 22 // been updated in the merged branch but not in the unmerged branch), 23 // this action may copy the /merged/ entry instead of the unmerged 24 // one. 25 type copyUnmergedEntryAction struct { 26 fromName data.PathPartString 27 toName data.PathPartString 28 symPath data.PathPartString 29 sizeOnly bool 30 unique bool 31 unmergedEntry data.DirEntry 32 attr []attrChange 33 } 34 35 func fixupNamesInOps(fromName string, toName string, ops []op, 36 chains *crChains) (retOps []op) { 37 retOps = make([]op, 0, len(ops)) 38 for _, uop := range ops { 39 done := false 40 switch realOp := uop.(type) { 41 // The only names that matter are in createOps or 42 // setAttrOps. rms on the unmerged side wouldn't be 43 // part of the unmerged entry 44 case *createOp: 45 if realOp.NewName == fromName { 46 realOpCopy := *realOp 47 realOpCopy.NewName = toName 48 retOps = append(retOps, &realOpCopy) 49 done = true 50 // Fix up the new name if this is a rename. 51 if realOp.renamed && len(realOp.Refs()) > 0 { 52 renamed := realOp.Refs()[0] 53 original, ok := chains.originals[renamed] 54 if !ok { 55 original = renamed 56 } 57 if ri, ok := chains.renamedOriginals[original]; ok { 58 ri.newName = toName 59 chains.renamedOriginals[original] = ri 60 } 61 } 62 } 63 case *setAttrOp: 64 if realOp.Name == fromName { 65 realOpCopy := *realOp 66 realOpCopy.Name = toName 67 retOps = append(retOps, &realOpCopy) 68 done = true 69 } 70 } 71 if !done { 72 retOps = append(retOps, uop) 73 } 74 } 75 return retOps 76 } 77 78 func (cuea *copyUnmergedEntryAction) swapUnmergedBlock( 79 ctx context.Context, unmergedChains, mergedChains *crChains, 80 unmergedDir *data.DirData) (bool, data.BlockPointer, error) { 81 if cuea.symPath.Plaintext() != "" { 82 return false, data.ZeroPtr, nil 83 } 84 85 unmergedEntry, err := unmergedDir.Lookup(ctx, cuea.fromName) 86 if err != nil { 87 return false, data.ZeroPtr, err 88 } 89 90 // If: 91 // * The entry BlockPointer has an unmerged chain with no (or 92 // attr-only) ops; AND 93 // * The entry BlockPointer does have a (non-deleted) merged chain 94 // copy the merged entry instead by fetching the block for its merged 95 // most recent parent and using that as the source, just copying over 96 // the "sizeAttr" fields. 97 ptr := unmergedEntry.BlockPointer 98 if chain, ok := unmergedChains.byMostRecent[ptr]; ok { 99 // If the chain has only setAttr ops, we still want to do the 100 // swap, but we need to preserve those unmerged attr changes. 101 for _, op := range chain.ops { 102 // As soon as we find an op that is NOT a setAttrOp, we 103 // should abort the swap. Otherwise save the changed 104 // attributes so we can re-apply them during do(). 105 if sao, ok := op.(*setAttrOp); ok { 106 cuea.attr = append(cuea.attr, sao.Attr) 107 } else { 108 return false, data.ZeroPtr, nil 109 } 110 } 111 ptr = chain.original 112 } 113 if _, ok := mergedChains.byOriginal[ptr]; !ok || 114 mergedChains.isDeleted(ptr) { 115 return false, data.ZeroPtr, nil 116 } 117 118 // If this entry was renamed, use the new parent; otherwise, 119 // return zeroPtr. 120 parentOrig, newName, ok := mergedChains.renamedParentAndName(ptr) 121 cuea.unmergedEntry = unmergedEntry 122 cuea.sizeOnly = true 123 if !ok { 124 // What about the unmerged branch? 125 ri, ok := unmergedChains.renamedOriginals[ptr] 126 if !ok { 127 return true, data.ZeroPtr, nil 128 } 129 parentOrig = ri.originalOldParent 130 newName = ri.oldName 131 } 132 parentMostRecent, err := 133 mergedChains.mostRecentFromOriginalOrSame(parentOrig) 134 if err != nil { 135 return false, data.ZeroPtr, err 136 } 137 cuea.fromName = data.NewPathPartString(newName, cuea.fromName.Obfuscator()) 138 return true, parentMostRecent, nil 139 } 140 141 func uniquifyName( 142 ctx context.Context, dir *data.DirData, name data.PathPartString) ( 143 data.PathPartString, error) { 144 _, err := dir.Lookup(ctx, name) 145 switch errors.Cause(err).(type) { 146 case nil: 147 case idutil.NoSuchNameError: 148 return name, nil 149 default: 150 return data.PathPartString{}, err 151 } 152 153 base, ext := data.SplitFileExtension(name.Plaintext()) 154 for i := 1; i <= 100; i++ { 155 newName := data.NewPathPartString( 156 fmt.Sprintf("%s (%d)%s", base, i, ext), name.Obfuscator()) 157 _, err := dir.Lookup(ctx, newName) 158 switch errors.Cause(err).(type) { 159 case nil: 160 case idutil.NoSuchNameError: 161 return newName, nil 162 default: 163 return data.PathPartString{}, err 164 } 165 } 166 167 return data.PathPartString{}, fmt.Errorf( 168 "Couldn't find a unique name for %s", name) 169 } 170 171 func (cuea *copyUnmergedEntryAction) do( 172 ctx context.Context, unmergedCopier, mergedCopier fileBlockDeepCopier, 173 unmergedDir, mergedDir *data.DirData) ([]data.BlockInfo, error) { 174 // Find the unmerged entry 175 unmergedEntry, err := unmergedDir.Lookup(ctx, cuea.fromName) 176 if err != nil { 177 return nil, err 178 } 179 180 if cuea.symPath.Plaintext() != "" { 181 unmergedEntry.Type = data.Sym 182 unmergedEntry.SymPath = cuea.symPath.Plaintext() 183 } 184 185 // Make sure this entry is unique. 186 if cuea.unique { 187 newName, err := uniquifyName(ctx, mergedDir, cuea.toName) 188 if err != nil { 189 return nil, err 190 } 191 cuea.toName = newName 192 } 193 194 mergedEntry, err := mergedDir.Lookup(ctx, cuea.toName) 195 mergedEntryOk := true 196 switch errors.Cause(err).(type) { 197 case nil: 198 case idutil.NoSuchNameError: 199 mergedEntryOk = false 200 default: 201 return nil, err 202 } 203 204 if cuea.sizeOnly { 205 if mergedEntryOk { 206 mergedEntry.Size = unmergedEntry.Size 207 mergedEntry.EncodedSize = unmergedEntry.EncodedSize 208 mergedEntry.BlockPointer = unmergedEntry.BlockPointer 209 return mergedDir.SetEntry(ctx, cuea.toName, mergedEntry) 210 } 211 // copy any attrs that were explicitly set on the unmerged 212 // branch. 213 for _, a := range cuea.attr { 214 switch a { 215 case exAttr: 216 unmergedEntry.Type = cuea.unmergedEntry.Type 217 case mtimeAttr: 218 unmergedEntry.Mtime = cuea.unmergedEntry.Mtime 219 } 220 } 221 } 222 223 // Throw out all the unmerged revisions and start over with the 224 // merged revisions. 225 if mergedEntryOk { 226 unmergedEntry.PrevRevisions = mergedEntry.PrevRevisions 227 } else { 228 unmergedEntry.PrevRevisions = nil 229 } 230 231 return mergedDir.SetEntry(ctx, cuea.toName, unmergedEntry) 232 } 233 234 func prependOpsToChain(mostRecent data.BlockPointer, chains *crChains, 235 newOps ...op) error { 236 chain := chains.byMostRecent[mostRecent] 237 // Create the chain if it doesn't exist yet. 238 if chain == nil && len(newOps) > 0 { 239 err := chains.makeChainForNewOp(mostRecent, newOps[0]) 240 if err != nil { 241 return err 242 } 243 chain = chains.byMostRecent[mostRecent] 244 chain.ops = nil // will prepend it below 245 } 246 for _, op := range newOps { 247 chain.ensurePath(op, mostRecent) 248 } 249 // prepend it 250 chain.ops = append(newOps, chain.ops...) 251 return nil 252 } 253 254 func crActionConvertSymlink(unmergedMostRecent data.BlockPointer, 255 mergedMostRecent data.BlockPointer, unmergedChain *crChain, 256 mergedChains *crChains, toName string) error { 257 co, err := newCreateOp(toName, mergedMostRecent, data.Sym) 258 if err != nil { 259 return err 260 } 261 262 // If the chain already exists, just append the operation instead 263 // of prepending them. If there was something else at that 264 // location in the unmerged branch, it should be moved out of the 265 // way first by a `renameOp` (see 266 // `makeLocalRenameOpForCopyAction()`). 267 chain, ok := mergedChains.byMostRecent[mergedMostRecent] 268 if ok { 269 chain.ensurePath(co, mergedMostRecent) 270 chain.ops = append(chain.ops, co) 271 } else { 272 err := prependOpsToChain(mergedMostRecent, mergedChains, co) 273 if err != nil { 274 return err 275 } 276 } 277 return nil 278 } 279 280 // trackSyncPtrChangesInCreate makes sure the correct set of refs and 281 // unrefs, from the syncOps on the unmerged branch, makes it into the 282 // createOp for a new file. 283 func trackSyncPtrChangesInCreate( 284 mostRecentTargetPtr data.BlockPointer, unmergedChain *crChain, 285 unmergedChains *crChains, toName string) { 286 targetChain, ok := unmergedChains.byMostRecent[mostRecentTargetPtr] 287 var refs, unrefs []data.BlockPointer 288 if ok && targetChain.isFile() { 289 // The create op also needs to reference the child block ptrs 290 // created by any sync ops (and not unreferenced by future 291 // ones). 292 for _, op := range targetChain.ops { 293 syncOp, ok := op.(*syncOp) 294 if !ok { 295 continue 296 } 297 for _, ref := range op.Refs() { 298 if !unmergedChains.isDeleted(ref) { 299 refs = append(refs, ref) 300 } 301 } 302 unrefs = append(unrefs, op.Unrefs()...) 303 // Account for the file ptr too, if it's the most recent. 304 filePtr := syncOp.File.Ref 305 _, isMostRecent := unmergedChains.byMostRecent[filePtr] 306 if isMostRecent && !unmergedChains.isDeleted(filePtr) { 307 refs = append(refs, filePtr) 308 } 309 } 310 } 311 if len(refs) > 0 { 312 for _, uop := range unmergedChain.ops { 313 cop, ok := uop.(*createOp) 314 if !ok || cop.NewName != toName { 315 continue 316 } 317 for _, ref := range refs { 318 cop.AddRefBlock(ref) 319 } 320 for _, unref := range unrefs { 321 cop.AddUnrefBlock(unref) 322 } 323 break 324 } 325 } 326 } 327 328 func (cuea *copyUnmergedEntryAction) trackSyncPtrChangesInCreate( 329 mostRecentTargetPtr data.BlockPointer, unmergedChain *crChain, 330 unmergedChains *crChains) { 331 trackSyncPtrChangesInCreate( 332 mostRecentTargetPtr, unmergedChain, unmergedChains, 333 cuea.toName.Plaintext()) 334 } 335 336 func makeLocalRenameOpForCopyAction( 337 ctx context.Context, mergedMostRecent data.BlockPointer, 338 mergedDir *data.DirData, mergedChains *crChains, fromName, 339 toName data.PathPartString) error { 340 newMergedEntry, err := mergedDir.Lookup(ctx, toName) 341 if err != nil { 342 return err 343 } 344 345 rop, err := newRenameOp( 346 fromName.Plaintext(), mergedMostRecent, toName.Plaintext(), 347 mergedMostRecent, newMergedEntry.BlockPointer, 348 newMergedEntry.Type) 349 if err != nil { 350 return err 351 } 352 err = prependOpsToChain(mergedMostRecent, mergedChains, rop) 353 if err != nil { 354 return err 355 } 356 return nil 357 } 358 359 func (cuea *copyUnmergedEntryAction) updateOps( 360 ctx context.Context, unmergedMostRecent, mergedMostRecent data.BlockPointer, 361 _, mergedDir *data.DirData, unmergedChains, mergedChains *crChains) error { 362 unmergedChain, ok := unmergedChains.byMostRecent[unmergedMostRecent] 363 if !ok { 364 return fmt.Errorf("Couldn't find unmerged chain for %v", 365 unmergedMostRecent) 366 } 367 368 if cuea.symPath.Plaintext() != "" && !unmergedChain.isFile() { 369 err := crActionConvertSymlink(unmergedMostRecent, mergedMostRecent, 370 unmergedChain, mergedChains, cuea.toName.Plaintext()) 371 if err != nil { 372 return err 373 } 374 } 375 376 // If the name changed, we have to update all the unmerged ops 377 // with the new name. 378 // The merged ops don't change, though later we may have to 379 // manipulate the block pointers in the original ops. 380 if cuea.fromName.Plaintext() != cuea.toName.Plaintext() { 381 unmergedChain.ops = fixupNamesInOps( 382 cuea.fromName.Plaintext(), cuea.toName.Plaintext(), 383 unmergedChain.ops, unmergedChains) 384 385 if cuea.unique || cuea.symPath.Plaintext() != "" { 386 // If a directory was renamed locally, either because of a 387 // direct conflict or because it was turned into a 388 // symlink, we need to fake a merged rename op from the 389 // unmerged name to the merged name, before creating the 390 // symlink, so the local Node objects are updated 391 // correctly. 392 err := makeLocalRenameOpForCopyAction( 393 ctx, mergedMostRecent, mergedDir, mergedChains, cuea.fromName, 394 cuea.toName) 395 if err != nil { 396 return err 397 } 398 } 399 } 400 401 // If the target is a file that had child blocks, we need to 402 // transfer those references over to the createOp. 403 mergedEntry, err := mergedDir.Lookup(ctx, cuea.toName) 404 if err != nil { 405 return err 406 } 407 mostRecentTargetPtr := mergedEntry.BlockPointer 408 cuea.trackSyncPtrChangesInCreate(mostRecentTargetPtr, unmergedChain, 409 unmergedChains) 410 411 return nil 412 } 413 414 func (cuea *copyUnmergedEntryAction) String() string { 415 return fmt.Sprintf("copyUnmergedEntry: %s -> %s %s", 416 cuea.fromName, cuea.toName, cuea.symPath) 417 } 418 419 // copyUnmergedAttrAction says that the given attributes in the 420 // unmerged entry for the given name should be copied directly into 421 // the merged version of the directory; there should be no conflict. 422 type copyUnmergedAttrAction struct { 423 fromName data.PathPartString 424 toName data.PathPartString 425 attr []attrChange 426 moved bool // move this action to the parent at most one time 427 } 428 429 func (cuaa *copyUnmergedAttrAction) swapUnmergedBlock( 430 _ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) { 431 return false, data.ZeroPtr, nil 432 } 433 434 func (cuaa *copyUnmergedAttrAction) do( 435 ctx context.Context, unmergedCopier, mergedCopier fileBlockDeepCopier, 436 unmergedDir, mergedDir *data.DirData) ([]data.BlockInfo, error) { 437 // Find the unmerged entry 438 unmergedEntry, err := unmergedDir.Lookup(ctx, cuaa.fromName) 439 if err != nil { 440 return nil, err 441 } 442 443 mergedEntry, err := mergedDir.Lookup(ctx, cuaa.toName) 444 if err != nil { 445 return nil, err 446 } 447 for _, attr := range cuaa.attr { 448 switch attr { 449 case exAttr: 450 mergedEntry.Type = unmergedEntry.Type 451 case mtimeAttr: 452 mergedEntry.Mtime = unmergedEntry.Mtime 453 case sizeAttr: 454 mergedEntry.Size = unmergedEntry.Size 455 mergedEntry.EncodedSize = unmergedEntry.EncodedSize 456 mergedEntry.BlockPointer = unmergedEntry.BlockPointer 457 } 458 } 459 460 return mergedDir.SetEntry(ctx, cuaa.toName, mergedEntry) 461 } 462 463 func (cuaa *copyUnmergedAttrAction) updateOps( 464 _ context.Context, unmergedMostRecent, _ data.BlockPointer, 465 _, _ *data.DirData, unmergedChains, _ *crChains) error { 466 unmergedChain, ok := unmergedChains.byMostRecent[unmergedMostRecent] 467 if !ok { 468 return fmt.Errorf("Couldn't find unmerged chain for %v", 469 unmergedMostRecent) 470 } 471 472 // If the name changed, we have to update all the unmerged ops 473 // with the new name. 474 // The merged ops don't change, though later we may have to 475 // manipulate the block pointers in the original ops. 476 if cuaa.fromName.Plaintext() != cuaa.toName.Plaintext() { 477 unmergedChain.ops = fixupNamesInOps( 478 cuaa.fromName.Plaintext(), cuaa.toName.Plaintext(), 479 unmergedChain.ops, unmergedChains) 480 } 481 return nil 482 } 483 484 func (cuaa *copyUnmergedAttrAction) String() string { 485 return fmt.Sprintf("copyUnmergedAttr: %s -> %s (%s)", 486 cuaa.fromName, cuaa.toName, cuaa.attr) 487 } 488 489 // rmMergedEntryAction says that the merged entry for the given name 490 // should be deleted. 491 type rmMergedEntryAction struct { 492 name data.PathPartString 493 } 494 495 func (rmea *rmMergedEntryAction) swapUnmergedBlock( 496 _ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) { 497 return false, data.ZeroPtr, nil 498 } 499 500 func (rmea *rmMergedEntryAction) do( 501 ctx context.Context, _, _ fileBlockDeepCopier, 502 _, mergedDir *data.DirData) ([]data.BlockInfo, error) { 503 unrefs, err := mergedDir.RemoveEntry(ctx, rmea.name) 504 if _, notExists := errors.Cause(err).(idutil.NoSuchNameError); notExists { 505 return nil, nil 506 } else if err != nil { 507 return nil, err 508 } 509 return unrefs, nil 510 } 511 512 func (rmea *rmMergedEntryAction) updateOps( 513 _ context.Context, _, _ data.BlockPointer, _, _ *data.DirData, _, _ *crChains) error { 514 return nil 515 } 516 517 func (rmea *rmMergedEntryAction) String() string { 518 return fmt.Sprintf("rmMergedEntry: %s", rmea.name) 519 } 520 521 // renameUnmergedAction says that the unmerged copy of a file needs to 522 // be renamed, and the file blocks should be copied. 523 type renameUnmergedAction struct { 524 fromName data.PathPartString 525 toName data.PathPartString 526 symPath data.PathPartString 527 causedByAttr attrChange // was this rename caused by a setAttr? 528 moved bool // move this action to the parent at most one time 529 530 // Set if this conflict is between file writes, and the parent 531 // chains need to be updated with new create/rename operations. 532 unmergedParentMostRecent data.BlockPointer 533 mergedParentMostRecent data.BlockPointer 534 } 535 536 func crActionCopyFile( 537 ctx context.Context, copier fileBlockDeepCopier, 538 fromName, toName, toSymPath data.PathPartString, 539 fromDir, toDir *data.DirData) ( 540 data.BlockPointer, data.PathPartString, []data.BlockInfo, error) { 541 // Find the source entry. 542 fromEntry, err := fromDir.Lookup(ctx, fromName) 543 if err != nil { 544 return data.BlockPointer{}, data.PathPartString{}, nil, err 545 } 546 547 if toSymPath.Plaintext() != "" { 548 fromEntry.Type = data.Sym 549 fromEntry.SymPath = toSymPath.Plaintext() 550 } 551 552 // We only rename files (or make symlinks to directories). 553 if fromEntry.Type == data.Dir { 554 // Just fill in the last path node, we don't have the full path. 555 return data.BlockPointer{}, data.PathPartString{}, nil, NotFileError{ 556 data.Path{Path: []data.PathNode{{ 557 BlockPointer: fromEntry.BlockPointer, 558 Name: fromName, 559 }}}} 560 } 561 562 // Make sure the name is unique. 563 name, err := uniquifyName(ctx, toDir, toName) 564 if err != nil { 565 return data.BlockPointer{}, data.PathPartString{}, nil, err 566 } 567 568 var ptr data.BlockPointer 569 if toSymPath.Plaintext() == "" && fromEntry.BlockPointer.IsInitialized() { 570 // Fetch the top block for copyable files. 571 var err error 572 ptr, err = copier(ctx, name, fromEntry.BlockPointer) 573 if err != nil { 574 return data.BlockPointer{}, data.PathPartString{}, nil, err 575 } 576 } 577 578 // Set the entry with the new pointer. 579 oldPointer := fromEntry.BlockPointer 580 fromEntry.BlockPointer = ptr 581 unrefs, err := toDir.SetEntry(ctx, name, fromEntry) 582 if err != nil { 583 return data.BlockPointer{}, data.PathPartString{}, nil, err 584 } 585 return oldPointer, name, unrefs, nil 586 } 587 588 func (rua *renameUnmergedAction) swapUnmergedBlock( 589 _ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) { 590 return false, data.ZeroPtr, nil 591 } 592 593 func (rua *renameUnmergedAction) do( 594 ctx context.Context, unmergedCopier, mergedCopier fileBlockDeepCopier, 595 unmergedDir, mergedDir *data.DirData) ([]data.BlockInfo, error) { 596 _, name, unrefs, err := crActionCopyFile( 597 ctx, unmergedCopier, rua.fromName, rua.toName, rua.symPath, 598 unmergedDir, mergedDir) 599 if err != nil { 600 return nil, err 601 } 602 rua.toName = name 603 return unrefs, nil 604 } 605 606 func removeRmOpFromChain( 607 original data.BlockPointer, chains *crChains, oldName string) { 608 chain := chains.byOriginal[original] 609 for i, op := range chain.ops { 610 if ro, ok := op.(*rmOp); !ok || ro.OldName != oldName { 611 continue 612 } 613 chain.ops = append(chain.ops[:i], chain.ops[i+1:]...) 614 } 615 } 616 617 func (rua *renameUnmergedAction) updateOps( 618 ctx context.Context, unmergedMostRecent, mergedMostRecent data.BlockPointer, 619 unmergedDir, mergedDir *data.DirData, 620 unmergedChains, mergedChains *crChains) error { 621 unmergedChain, ok := unmergedChains.byMostRecent[unmergedMostRecent] 622 if !ok { 623 return fmt.Errorf("Couldn't find unmerged chain for %v", 624 unmergedMostRecent) 625 } 626 627 unmergedEntry, err := unmergedDir.Lookup(ctx, rua.fromName) 628 if err != nil { 629 return err 630 } 631 original, err := unmergedChains.originalFromMostRecentOrSame( 632 unmergedEntry.BlockPointer) 633 if err != nil { 634 return err 635 } 636 637 if rua.symPath.Plaintext() != "" && !unmergedChain.isFile() { 638 err := crActionConvertSymlink(unmergedMostRecent, mergedMostRecent, 639 unmergedChain, mergedChains, rua.toName.Plaintext()) 640 if err != nil { 641 return err 642 } 643 } 644 645 // Rename all operations with the old name to the new name. 646 unmergedChain.ops = fixupNamesInOps( 647 rua.fromName.Plaintext(), rua.toName.Plaintext(), unmergedChain.ops, 648 unmergedChains) 649 650 // The newly renamed entry: 651 newMergedEntry, err := mergedDir.Lookup(ctx, rua.toName) 652 if err != nil { 653 return err 654 } 655 656 if unmergedChain.isFile() { 657 // Replace the updates on all file operations. 658 for _, op := range unmergedChain.ops { 659 switch realOp := op.(type) { 660 case *syncOp: 661 // Only apply to syncs that match the pointer chain 662 // for which `updateOps` was called. 663 opOriginal, ok := unmergedChains.originals[realOp.File.Ref] 664 if !ok { 665 opOriginal = realOp.File.Ref 666 } 667 if opOriginal != original { 668 continue 669 } 670 671 var err error 672 realOp.File, err = makeBlockUpdate( 673 newMergedEntry.BlockPointer, 674 newMergedEntry.BlockPointer) 675 if err != nil { 676 return err 677 } 678 // Nuke the previously referenced blocks, they are no 679 // longer relevant. 680 realOp.RefBlocks = nil 681 case *setAttrOp: 682 // Only apply to setAttrs that match the pointer chain 683 // for which `updateOps` was called. 684 opOriginal, ok := unmergedChains.originals[realOp.File] 685 if !ok { 686 opOriginal = realOp.File 687 } 688 if opOriginal != original { 689 continue 690 } 691 692 realOp.File = newMergedEntry.BlockPointer 693 } 694 } 695 696 if !rua.unmergedParentMostRecent.IsInitialized() { 697 // This is not a file-file conflict. 698 return nil 699 } 700 701 unmergedChain, ok = 702 unmergedChains.byMostRecent[rua.unmergedParentMostRecent] 703 if !ok { 704 // Couldn't find the parent to update. Sigh. 705 return fmt.Errorf("Couldn't find parent %v to update "+ 706 "renameUnmergedAction for file %v (%s)", 707 rua.unmergedParentMostRecent, unmergedMostRecent, rua.toName) 708 } 709 unmergedMostRecent = rua.unmergedParentMostRecent 710 mergedMostRecent = rua.mergedParentMostRecent 711 } 712 713 // Prepend a rename for the unmerged copy to the merged set of 714 // operations, with another create for the merged file, for local 715 // playback. 716 717 // The entry that gets created in the unmerged branch: 718 mergedEntry, err := mergedDir.Lookup(ctx, rua.fromName) 719 if err != nil { 720 return err 721 } 722 723 _, mergedRename := mergedChains.renamedOriginals[original] 724 if rua.toName.Plaintext() == rua.fromName.Plaintext() && mergedRename { 725 // The merged copy is the one changing its name, so turn the 726 // merged rename op into just a create by removing the rmOp. 727 removeRmOpFromChain( 728 unmergedChain.original, mergedChains, rua.toName.Plaintext()) 729 if rua.symPath.Plaintext() == "" { 730 // Pretend to write to the new, deduplicated unmerged 731 // copy, to update its pointer in the node cache. 732 so, err := newSyncOp(unmergedEntry.BlockPointer) 733 if err != nil { 734 return err 735 } 736 so.AddUpdate( 737 unmergedEntry.BlockPointer, newMergedEntry.BlockPointer) 738 err = prependOpsToChain( 739 unmergedEntry.BlockPointer, mergedChains, so) 740 if err != nil { 741 return err 742 } 743 } 744 } else { 745 // The unmerged copy is changing its name, so make a local 746 // rename op for it, and a create op for the merged version. 747 rop, err := newRenameOp( 748 rua.fromName.Plaintext(), mergedMostRecent, rua.toName.Plaintext(), 749 mergedMostRecent, newMergedEntry.BlockPointer, newMergedEntry.Type) 750 if err != nil { 751 return err 752 } 753 // For local notifications, we need to transform the entry's 754 // pointer into the new (de-dup'd) pointer. newMergedEntry is 755 // not yet the final pointer (that happens during syncBlock), 756 // but a later stage will convert it. 757 if rua.symPath.Plaintext() == "" { 758 rop.AddUpdate( 759 unmergedEntry.BlockPointer, newMergedEntry.BlockPointer) 760 } 761 co, err := newCreateOp( 762 rua.fromName.Plaintext(), mergedMostRecent, mergedEntry.Type) 763 if err != nil { 764 return err 765 } 766 err = prependOpsToChain(mergedMostRecent, mergedChains, rop, co) 767 if err != nil { 768 return err 769 } 770 } 771 772 // Before merging the unmerged ops, create a file with the new 773 // name, unless the create already exists. 774 found := false 775 var co *createOp 776 for _, op := range unmergedChain.ops { 777 var ok bool 778 if co, ok = op.(*createOp); ok && co.NewName == rua.toName.Plaintext() { 779 found = true 780 if len(co.RefBlocks) > 0 { 781 co.RefBlocks[0] = newMergedEntry.BlockPointer 782 } 783 break 784 } 785 } 786 if !found { 787 co, err = newCreateOp( 788 rua.toName.Plaintext(), unmergedMostRecent, mergedEntry.Type) 789 if err != nil { 790 return err 791 } 792 if rua.symPath.Plaintext() == "" { 793 co.AddRefBlock(newMergedEntry.BlockPointer) 794 } 795 err = prependOpsToChain(unmergedMostRecent, unmergedChains, co) 796 if err != nil { 797 return err 798 } 799 } 800 // Since we copied the node, unref the old block but only if 801 // it's not a symlink and the name changed. If the name is 802 // the same, it means the old block pointer is still in use 803 // because we just did a copy of a node still in use in the 804 // merged branch. 805 if unmergedEntry.BlockPointer != newMergedEntry.BlockPointer && 806 rua.fromName.Plaintext() != rua.toName.Plaintext() && 807 rua.symPath.Plaintext() == "" { 808 co.AddUnrefBlock(unmergedEntry.BlockPointer) 809 orig, ok := unmergedChains.originals[unmergedEntry.BlockPointer] 810 if !ok { 811 orig = unmergedEntry.BlockPointer 812 } 813 unmergedChains.deletedOriginals[orig] = true 814 } 815 816 return nil 817 } 818 819 func (rua *renameUnmergedAction) String() string { 820 return fmt.Sprintf("renameUnmerged: %s -> %s %s", rua.fromName, rua.toName, 821 rua.symPath) 822 } 823 824 // renameMergedAction says that the merged copy of a file needs to be 825 // renamed, and the unmerged entry should be added to the merged block 826 // under the old from name. Merged file blocks do not have to be 827 // copied, because renaming a merged file can only happen when the 828 // conflict is with a non-file in the unmerged branch; thus, there can 829 // be no shared blocks between the two. 830 // 831 // Note that symPath below refers to the unmerged entry that is being 832 // copied into the merged block. 833 type renameMergedAction struct { 834 fromName data.PathPartString 835 toName data.PathPartString 836 symPath data.PathPartString 837 } 838 839 func (rma *renameMergedAction) swapUnmergedBlock( 840 _ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) { 841 return false, data.ZeroPtr, nil 842 } 843 844 func (rma *renameMergedAction) do( 845 ctx context.Context, unmergedCopier, mergedCopier fileBlockDeepCopier, 846 unmergedDir, mergedDir *data.DirData) ([]data.BlockInfo, error) { 847 // Find the merged entry 848 mergedEntry, err := mergedDir.Lookup(ctx, rma.fromName) 849 if err != nil { 850 return nil, err 851 } 852 853 // Make sure this entry is unique. 854 newName, err := uniquifyName(ctx, mergedDir, rma.toName) 855 if err != nil { 856 return nil, err 857 } 858 rma.toName = newName 859 860 unrefs, err := mergedDir.SetEntry(ctx, rma.toName, mergedEntry) 861 if err != nil { 862 return nil, err 863 } 864 865 // Add the unmerged entry as the new "fromName". 866 unmergedEntry, err := unmergedDir.Lookup(ctx, rma.fromName) 867 if err != nil { 868 return nil, err 869 } 870 if rma.symPath.Plaintext() != "" { 871 unmergedEntry.Type = data.Sym 872 unmergedEntry.SymPath = rma.symPath.Plaintext() 873 } 874 unmergedEntry.PrevRevisions = nil 875 876 moreUnrefs, err := mergedDir.SetEntry(ctx, rma.fromName, unmergedEntry) 877 if err != nil { 878 return nil, err 879 } 880 unrefs = append(unrefs, moreUnrefs...) 881 return unrefs, nil 882 } 883 884 func (rma *renameMergedAction) updateOps( 885 ctx context.Context, unmergedMostRecent, mergedMostRecent data.BlockPointer, 886 unmergedDir, mergedDir *data.DirData, 887 unmergedChains *crChains, mergedChains *crChains) error { 888 unmergedChain, ok := unmergedChains.byMostRecent[unmergedMostRecent] 889 if !ok { 890 return fmt.Errorf("Couldn't find unmerged chain for %v", 891 unmergedMostRecent) 892 } 893 894 if rma.symPath.Plaintext() != "" && !unmergedChain.isFile() { 895 err := crActionConvertSymlink(unmergedMostRecent, mergedMostRecent, 896 unmergedChain, mergedChains, rma.fromName.Plaintext()) 897 if err != nil { 898 return err 899 } 900 } 901 902 // Rename all operations with the old name to the new name. 903 mergedChain := mergedChains.byMostRecent[mergedMostRecent] 904 if mergedChain != nil { 905 mergedChain.ops = fixupNamesInOps( 906 rma.fromName.Plaintext(), rma.toName.Plaintext(), mergedChain.ops, 907 mergedChains) 908 } 909 910 if !unmergedChain.isFile() { 911 // The entry that gets renamed in the unmerged branch: 912 mergedEntry, err := mergedDir.Lookup(ctx, rma.toName) 913 if err != nil { 914 return err 915 } 916 917 // Prepend a rename for the merged copy to the unmerged set of 918 // operations. 919 rop, err := newRenameOp( 920 rma.fromName.Plaintext(), unmergedMostRecent, 921 rma.toName.Plaintext(), unmergedMostRecent, 922 mergedEntry.BlockPointer, mergedEntry.Type) 923 if err != nil { 924 return err 925 } 926 err = prependOpsToChain(unmergedMostRecent, unmergedChains, rop) 927 if err != nil { 928 return err 929 } 930 931 // Before playing back the merged ops, create a file with the 932 // new name, unless the create already exists. 933 found := false 934 if mergedChain != nil { 935 for _, op := range mergedChain.ops { 936 if co, ok := op.(*createOp); ok && 937 co.NewName == rma.toName.Plaintext() { 938 found = true 939 break 940 } 941 } 942 } 943 if !found { 944 co, err := newCreateOp( 945 rma.toName.Plaintext(), mergedMostRecent, mergedEntry.Type) 946 if err != nil { 947 return err 948 } 949 err = prependOpsToChain(mergedMostRecent, mergedChains, co) 950 if err != nil { 951 return err 952 } 953 } 954 } 955 956 return nil 957 } 958 959 func (rma *renameMergedAction) String() string { 960 return fmt.Sprintf("renameMerged: %s -> %s", rma.fromName, rma.toName) 961 } 962 963 // dropUnmergedAction says that the corresponding unmerged 964 // operation should be dropped. 965 type dropUnmergedAction struct { 966 op op 967 } 968 969 func (dua *dropUnmergedAction) swapUnmergedBlock( 970 _ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) { 971 return false, data.ZeroPtr, nil 972 } 973 974 func (dua *dropUnmergedAction) do( 975 _ context.Context, _, _ fileBlockDeepCopier, _, _ *data.DirData) ( 976 []data.BlockInfo, error) { 977 return nil, nil 978 } 979 980 func (dua *dropUnmergedAction) updateOps( 981 _ context.Context, unmergedMostRecent, mergedMostRecent data.BlockPointer, 982 _, _ *data.DirData, unmergedChains, mergedChains *crChains) error { 983 unmergedChain, ok := unmergedChains.byMostRecent[unmergedMostRecent] 984 if !ok { 985 return fmt.Errorf("Couldn't find unmerged chain for %v", 986 unmergedMostRecent) 987 } 988 989 found := false 990 for i, op := range unmergedChain.ops { 991 if op == dua.op { 992 unmergedChain.ops = 993 append(unmergedChain.ops[:i], unmergedChain.ops[i+1:]...) 994 found = true 995 break 996 } 997 } 998 999 // Return early if this chain didn't contain the op; no need to 1000 // invert on the merged chain in that case. 1001 if !found { 1002 return nil 1003 } 1004 1005 invertedOp, err := invertOpForLocalNotifications(dua.op) 1006 if err != nil { 1007 return err 1008 } 1009 err = prependOpsToChain(mergedMostRecent, mergedChains, invertedOp) 1010 if err != nil { 1011 return err 1012 } 1013 return nil 1014 } 1015 1016 func (dua *dropUnmergedAction) String() string { 1017 return fmt.Sprintf("dropUnmerged: %s", dua.op) 1018 } 1019 1020 type collapseActionInfo struct { 1021 topAction crAction 1022 topActionIndex int 1023 } 1024 1025 type crActionList []crAction 1026 1027 func setTopAction(action crAction, fromName string, index int, 1028 infoMap map[string]collapseActionInfo, indicesToRemove map[int]bool) { 1029 info, ok := infoMap[fromName] 1030 if ok { 1031 indicesToRemove[info.topActionIndex] = true 1032 } 1033 info.topAction = action 1034 info.topActionIndex = index 1035 infoMap[fromName] = info 1036 } 1037 1038 // collapse drops any actions that are made irrelevant by other 1039 // actions in the list. It assumes that file-related actions have 1040 // already been merged into their parent directory action lists. 1041 func (cal crActionList) collapse() crActionList { 1042 // Order of precedence for a given fromName: 1043 // 1) renameUnmergedAction 1044 // 2) copyUnmergedEntryAction 1045 // 3) copyUnmergedAttrAction 1046 infoMap := make(map[string]collapseActionInfo) // fromName -> info 1047 indicesToRemove := make(map[int]bool) 1048 for i, untypedAction := range cal { 1049 switch action := untypedAction.(type) { 1050 1051 // Unmerged actions: 1052 case *renameUnmergedAction: 1053 setTopAction( 1054 action, action.fromName.Plaintext(), i, infoMap, 1055 indicesToRemove) 1056 case *copyUnmergedEntryAction: 1057 untypedTopAction := infoMap[action.fromName.Plaintext()].topAction 1058 switch untypedTopAction.(type) { 1059 case *renameUnmergedAction: 1060 indicesToRemove[i] = true 1061 default: 1062 setTopAction( 1063 action, action.fromName.Plaintext(), i, infoMap, 1064 indicesToRemove) 1065 } 1066 case *copyUnmergedAttrAction: 1067 untypedTopAction := infoMap[action.fromName.Plaintext()].topAction 1068 switch topAction := untypedTopAction.(type) { 1069 case *renameUnmergedAction: 1070 indicesToRemove[i] = true 1071 case *copyUnmergedEntryAction: 1072 indicesToRemove[i] = true 1073 case *copyUnmergedAttrAction: 1074 // Add attributes to the current top action, if not 1075 // already there. 1076 for _, a := range action.attr { 1077 found := false 1078 for _, topA := range topAction.attr { 1079 if a == topA { 1080 found = true 1081 break 1082 } 1083 } 1084 if !found { 1085 topAction.attr = append(topAction.attr, a) 1086 } 1087 } 1088 indicesToRemove[i] = true 1089 default: 1090 setTopAction( 1091 action, action.fromName.Plaintext(), i, infoMap, 1092 indicesToRemove) 1093 } 1094 1095 // Merged actions 1096 case *renameMergedAction: 1097 // Prefix merged actions with a reserved prefix to keep 1098 // them separate from the unmerged actions. 1099 setTopAction( 1100 action, ".kbfs_merged_"+action.fromName.Plaintext(), i, infoMap, 1101 indicesToRemove) 1102 } 1103 } 1104 1105 if len(indicesToRemove) == 0 { 1106 return cal 1107 } 1108 1109 newList := make(crActionList, 0, len(cal)-len(indicesToRemove)) 1110 for i, action := range cal { 1111 if indicesToRemove[i] { 1112 continue 1113 } 1114 newList = append(newList, action) 1115 } 1116 1117 return newList 1118 }