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
5package libkbfs
6
7import (
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.
25type 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
35func 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
78func (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
141func 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
171func (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
234func 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
254func 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.
283func 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
328func (cuea *copyUnmergedEntryAction) trackSyncPtrChangesInCreate(
329	mostRecentTargetPtr data.BlockPointer, unmergedChain *crChain,
330	unmergedChains *crChains) {
331	trackSyncPtrChangesInCreate(
332		mostRecentTargetPtr, unmergedChain, unmergedChains,
333		cuea.toName.Plaintext())
334}
335
336func 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
359func (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
414func (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.
422type 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
429func (cuaa *copyUnmergedAttrAction) swapUnmergedBlock(
430	_ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) {
431	return false, data.ZeroPtr, nil
432}
433
434func (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
463func (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
484func (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.
491type rmMergedEntryAction struct {
492	name data.PathPartString
493}
494
495func (rmea *rmMergedEntryAction) swapUnmergedBlock(
496	_ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) {
497	return false, data.ZeroPtr, nil
498}
499
500func (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
512func (rmea *rmMergedEntryAction) updateOps(
513	_ context.Context, _, _ data.BlockPointer, _, _ *data.DirData, _, _ *crChains) error {
514	return nil
515}
516
517func (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.
523type 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
536func 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
588func (rua *renameUnmergedAction) swapUnmergedBlock(
589	_ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) {
590	return false, data.ZeroPtr, nil
591}
592
593func (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
606func 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
617func (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
819func (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.
833type renameMergedAction struct {
834	fromName data.PathPartString
835	toName   data.PathPartString
836	symPath  data.PathPartString
837}
838
839func (rma *renameMergedAction) swapUnmergedBlock(
840	_ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) {
841	return false, data.ZeroPtr, nil
842}
843
844func (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
884func (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
959func (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.
965type dropUnmergedAction struct {
966	op op
967}
968
969func (dua *dropUnmergedAction) swapUnmergedBlock(
970	_ context.Context, _, _ *crChains, _ *data.DirData) (bool, data.BlockPointer, error) {
971	return false, data.ZeroPtr, nil
972}
973
974func (dua *dropUnmergedAction) do(
975	_ context.Context, _, _ fileBlockDeepCopier, _, _ *data.DirData) (
976	[]data.BlockInfo, error) {
977	return nil, nil
978}
979
980func (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
1016func (dua *dropUnmergedAction) String() string {
1017	return fmt.Sprintf("dropUnmerged: %s", dua.op)
1018}
1019
1020type collapseActionInfo struct {
1021	topAction      crAction
1022	topActionIndex int
1023}
1024
1025type crActionList []crAction
1026
1027func 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.
1041func (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}
1119