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  }