/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

Merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
"""
19
19
 
20
20
import os
 
21
from collections import deque
21
22
from cStringIO import StringIO
22
23
 
23
24
import bzrlib
29
30
from bzrlib.decorators import needs_read_lock
30
31
from bzrlib.errors import BzrError, BzrCheckError
31
32
from bzrlib import errors
32
 
from bzrlib.inventory import Inventory
 
33
from bzrlib.inventory import Inventory, InventoryFile
33
34
from bzrlib.inter import InterObject
34
35
from bzrlib.osutils import fingerprint_file
35
36
import bzrlib.revision
57
58
    """
58
59
    
59
60
    def changes_from(self, other, want_unchanged=False, specific_files=None,
60
 
        extra_trees=None, require_versioned=False, include_root=False):
 
61
        extra_trees=None, require_versioned=False, include_root=False,
 
62
        want_unversioned=False):
61
63
        """Return a TreeDelta of the changes from other to this tree.
62
64
 
63
65
        :param other: A tree to compare with.
72
74
        :param require_versioned: An optional boolean (defaults to False). When
73
75
            supplied and True all the 'specific_files' must be versioned, or
74
76
            a PathsNotVersionedError will be thrown.
 
77
        :param want_unversioned: Scan for unversioned paths.
75
78
 
76
79
        The comparison will be performed by an InterTree object looked up on 
77
80
        self and other.
84
87
            specific_files=specific_files,
85
88
            extra_trees=extra_trees,
86
89
            require_versioned=require_versioned,
87
 
            include_root=include_root
 
90
            include_root=include_root,
 
91
            want_unversioned=want_unversioned,
88
92
            )
89
93
 
90
 
    def _iter_changes(self, from_tree, include_unchanged=False, 
91
 
                     specific_file_ids=None, pb=None):
 
94
    def _iter_changes(self, from_tree, include_unchanged=False,
 
95
                     specific_files=None, pb=None, extra_trees=None,
 
96
                     require_versioned=True, want_unversioned=False):
92
97
        intertree = InterTree.get(from_tree, self)
93
 
        return intertree._iter_changes(from_tree, self, include_unchanged, 
94
 
                                       specific_file_ids, pb)
 
98
        return intertree._iter_changes(include_unchanged, specific_files, pb,
 
99
            extra_trees, require_versioned, want_unversioned=want_unversioned)
95
100
    
96
101
    def conflicts(self):
97
102
        """Get a list of the conflicts in the tree.
100
105
        """
101
106
        return []
102
107
 
 
108
    def extras(self):
 
109
        """For trees that can have unversioned files, return all such paths."""
 
110
        return []
 
111
 
103
112
    def get_parent_ids(self):
104
113
        """Get the parent ids for this tree. 
105
114
 
114
123
        raise NotImplementedError()
115
124
 
116
125
    def has_id(self, file_id):
 
126
        file_id = osutils.safe_file_id(file_id)
117
127
        return self.inventory.has_id(file_id)
118
128
 
119
129
    __contains__ = has_id
120
130
 
121
131
    def has_or_had_id(self, file_id):
 
132
        file_id = osutils.safe_file_id(file_id)
122
133
        if file_id == self.inventory.root.file_id:
123
134
            return True
124
135
        return self.inventory.has_id(file_id)
125
136
 
 
137
    def is_ignored(self, filename):
 
138
        """Check whether the filename is ignored by this tree.
 
139
 
 
140
        :param filename: The relative filename within the tree.
 
141
        :return: True if the filename is ignored.
 
142
        """
 
143
        return False
 
144
 
126
145
    def __iter__(self):
127
146
        return iter(self.inventory)
128
147
 
129
148
    def id2path(self, file_id):
 
149
        """Return the path for a file id.
 
150
 
 
151
        :raises NoSuchId:
 
152
        """
 
153
        file_id = osutils.safe_file_id(file_id)
130
154
        return self.inventory.id2path(file_id)
131
155
 
132
156
    def is_control_filename(self, filename):
141
165
        """
142
166
        return self.bzrdir.is_control_filename(filename)
143
167
 
 
168
    @needs_read_lock
144
169
    def iter_entries_by_dir(self, specific_file_ids=None):
145
170
        """Walk the tree in 'by_dir' order.
146
171
 
152
177
        return self.inventory.iter_entries_by_dir(
153
178
            specific_file_ids=specific_file_ids)
154
179
 
 
180
    def iter_references(self):
 
181
        for path, entry in self.iter_entries_by_dir():
 
182
            if entry.kind == 'tree-reference':
 
183
                yield path, entry.file_id
 
184
 
155
185
    def kind(self, file_id):
156
 
        raise NotImplementedError("subclasses must implement kind")
 
186
        raise NotImplementedError("Tree subclass %s must implement kind"
 
187
            % self.__class__.__name__)
 
188
 
 
189
    def get_reference_revision(self, file_id, path=None):
 
190
        raise NotImplementedError("Tree subclass %s must implement "
 
191
                                  "get_reference_revision"
 
192
            % self.__class__.__name__)
157
193
 
158
194
    def _comparison_data(self, entry, path):
159
195
        """Return a tuple of kind, executable, stat_value for a file.
166
202
        """
167
203
        raise NotImplementedError(self._comparison_data)
168
204
 
169
 
    def _file_size(entry, stat_value):
 
205
    def _file_size(self, entry, stat_value):
170
206
        raise NotImplementedError(self._file_size)
171
207
 
172
208
    def _get_inventory(self):
175
211
    def get_file(self, file_id):
176
212
        """Return a file object for the file file_id in the tree."""
177
213
        raise NotImplementedError(self.get_file)
178
 
    
 
214
 
 
215
    def get_file_mtime(self, file_id, path=None):
 
216
        """Return the modification time for a file.
 
217
 
 
218
        :param file_id: The handle for this file.
 
219
        :param path: The path that this file can be found at.
 
220
            These must point to the same object.
 
221
        """
 
222
        raise NotImplementedError(self.get_file_mtime)
 
223
 
179
224
    def get_file_by_path(self, path):
180
225
        return self.get_file(self._inventory.path2id(path))
181
226
 
 
227
    def get_symlink_target(self, file_id):
 
228
        """Get the target for a given file_id.
 
229
 
 
230
        It is assumed that the caller already knows that file_id is referencing
 
231
        a symlink.
 
232
        :param file_id: Handle for the symlink entry.
 
233
        :return: The path the symlink points to.
 
234
        """
 
235
        raise NotImplementedError(self.get_symlink_target)
 
236
 
182
237
    def annotate_iter(self, file_id):
183
238
        """Return an iterator of revision_id, line tuples
184
239
 
215
270
        """Return the id for path in this tree."""
216
271
        return self._inventory.path2id(path)
217
272
 
 
273
    def paths2ids(self, paths, trees=[], require_versioned=True):
 
274
        """Return all the ids that can be reached by walking from paths.
 
275
        
 
276
        Each path is looked up in each this tree and any extras provided in
 
277
        trees, and this is repeated recursively: the children in an extra tree
 
278
        of a directory that has been renamed under a provided path in this tree
 
279
        are all returned, even if none exist until a provided path in this
 
280
        tree, and vice versa.
 
281
 
 
282
        :param paths: An iterable of paths to start converting to ids from.
 
283
            Alternatively, if paths is None, no ids should be calculated and None
 
284
            will be returned. This is offered to make calling the api unconditional
 
285
            for code that *might* take a list of files.
 
286
        :param trees: Additional trees to consider.
 
287
        :param require_versioned: If False, do not raise NotVersionedError if
 
288
            an element of paths is not versioned in this tree and all of trees.
 
289
        """
 
290
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
 
291
 
218
292
    def print_file(self, file_id):
219
293
        """Print file with id `file_id` to stdout."""
 
294
        file_id = osutils.safe_file_id(file_id)
220
295
        import sys
221
296
        sys.stdout.write(self.get_file_text(file_id))
222
297
 
223
298
    def lock_read(self):
224
299
        pass
225
300
 
 
301
    def revision_tree(self, revision_id):
 
302
        """Obtain a revision tree for the revision revision_id.
 
303
 
 
304
        The intention of this method is to allow access to possibly cached
 
305
        tree data. Implementors of this method should raise NoSuchRevision if
 
306
        the tree is not locally available, even if they could obtain the 
 
307
        tree via a repository or some other means. Callers are responsible 
 
308
        for finding the ultimate source for a revision tree.
 
309
 
 
310
        :param revision_id: The revision_id of the requested tree.
 
311
        :return: A Tree.
 
312
        :raises: NoSuchRevision if the tree cannot be obtained.
 
313
        """
 
314
        raise errors.NoSuchRevisionInTree(self, revision_id)
 
315
 
226
316
    def unknowns(self):
227
317
        """What files are present in this tree and unknown.
228
318
        
234
324
        pass
235
325
 
236
326
    def filter_unversioned_files(self, paths):
237
 
        """Filter out paths that are not versioned.
 
327
        """Filter out paths that are versioned.
238
328
 
239
329
        :return: set of paths.
240
330
        """
244
334
        pred = self.inventory.has_filename
245
335
        return set((p for p in paths if not pred(p)))
246
336
 
 
337
    def walkdirs(self, prefix=""):
 
338
        """Walk the contents of this tree from path down.
 
339
 
 
340
        This yields all the data about the contents of a directory at a time.
 
341
        After each directory has been yielded, if the caller has mutated the
 
342
        list to exclude some directories, they are then not descended into.
 
343
        
 
344
        The data yielded is of the form:
 
345
        ((directory-relpath, directory-path-from-root, directory-fileid),
 
346
        [(relpath, basename, kind, lstat, path_from_tree_root, file_id, 
 
347
          versioned_kind), ...]),
 
348
         - directory-relpath is the containing dirs relpath from prefix
 
349
         - directory-path-from-root is the containing dirs path from /
 
350
         - directory-fileid is the id of the directory if it is versioned.
 
351
         - relpath is the relative path within the subtree being walked.
 
352
         - basename is the basename
 
353
         - kind is the kind of the file now. If unknonwn then the file is not
 
354
           present within the tree - but it may be recorded as versioned. See
 
355
           versioned_kind.
 
356
         - lstat is the stat data *if* the file was statted.
 
357
         - path_from_tree_root is the path from the root of the tree.
 
358
         - file_id is the file_id is the entry is versioned.
 
359
         - versioned_kind is the kind of the file as last recorded in the 
 
360
           versioning system. If 'unknown' the file is not versioned.
 
361
        One of 'kind' and 'versioned_kind' must not be 'unknown'.
 
362
 
 
363
        :param prefix: Start walking from prefix within the tree rather than
 
364
        at the root. This allows one to walk a subtree but get paths that are
 
365
        relative to a tree rooted higher up.
 
366
        :return: an iterator over the directory data.
 
367
        """
 
368
        raise NotImplementedError(self.walkdirs)
 
369
 
247
370
 
248
371
class EmptyTree(Tree):
249
372
 
263
386
        return False
264
387
 
265
388
    def kind(self, file_id):
 
389
        file_id = osutils.safe_file_id(file_id)
266
390
        assert self._inventory[file_id].kind == "directory"
267
391
        return "directory"
268
392
 
270
394
        return iter([])
271
395
    
272
396
    def __contains__(self, file_id):
 
397
        file_id = osutils.safe_file_id(file_id)
273
398
        return (file_id in self._inventory)
274
399
 
275
400
    def get_file_sha1(self, file_id, path=None, stat_value=None):
356
481
    """
357
482
    if not filenames:
358
483
        return None
359
 
    specified_ids = _find_filename_ids_across_trees(filenames, trees, 
360
 
                                                    require_versioned)
361
 
    return _find_children_across_trees(specified_ids, trees)
362
 
 
363
 
 
364
 
def _find_filename_ids_across_trees(filenames, trees, require_versioned):
 
484
    specified_path_ids = _find_ids_across_trees(filenames, trees,
 
485
        require_versioned)
 
486
    return _find_children_across_trees(specified_path_ids, trees)
 
487
 
 
488
 
 
489
def _find_ids_across_trees(filenames, trees, require_versioned):
365
490
    """Find the ids corresponding to specified filenames.
366
491
    
367
 
    All matches in all trees will be used.
 
492
    All matches in all trees will be used, but subdirectories are not scanned.
368
493
 
369
494
    :param filenames: The filenames to find file_ids for
370
495
    :param trees: The trees to find file_ids within
371
496
    :param require_versioned: if true, all specified filenames must occur in
372
 
    at least one tree.
373
 
    :return: a set of file ids for the specified filenames
 
497
        at least one tree.
 
498
    :return: a set of (path, file ids) for the specified filenames
374
499
    """
375
500
    not_versioned = []
376
501
    interesting_ids = set()
377
502
    for tree_path in filenames:
378
503
        not_found = True
379
504
        for tree in trees:
380
 
            file_id = tree.inventory.path2id(tree_path)
 
505
            file_id = tree.path2id(tree_path)
381
506
            if file_id is not None:
382
507
                interesting_ids.add(file_id)
383
508
                not_found = False
404
529
        new_pending = set()
405
530
        for file_id in pending:
406
531
            for tree in trees:
407
 
                if file_id not in tree:
 
532
                if not tree.has_id(file_id):
408
533
                    continue
409
534
                entry = tree.inventory[file_id]
410
535
                for child in getattr(entry, 'children', {}).itervalues():
430
555
 
431
556
    @needs_read_lock
432
557
    def compare(self, want_unchanged=False, specific_files=None,
433
 
        extra_trees=None, require_versioned=False, include_root=False):
 
558
        extra_trees=None, require_versioned=False, include_root=False,
 
559
        want_unversioned=False):
434
560
        """Return the changes from source to target.
435
561
 
436
562
        :return: A TreeDelta.
445
571
        :param require_versioned: An optional boolean (defaults to False). When
446
572
            supplied and True all the 'specific_files' must be versioned, or
447
573
            a PathsNotVersionedError will be thrown.
 
574
        :param want_unversioned: Scan for unversioned paths.
448
575
        """
449
 
        # NB: show_status depends on being able to pass in non-versioned files and
450
 
        # report them as unknown
451
 
        trees = (self.source, self.target)
 
576
        # NB: show_status depends on being able to pass in non-versioned files
 
577
        # and report them as unknown
 
578
        trees = (self.source,)
452
579
        if extra_trees is not None:
453
580
            trees = trees + tuple(extra_trees)
454
 
        specific_file_ids = find_ids_across_trees(specific_files,
455
 
            trees, require_versioned=require_versioned)
 
581
        # target is usually the newer tree:
 
582
        specific_file_ids = self.target.paths2ids(specific_files, trees,
 
583
            require_versioned=require_versioned)
456
584
        if specific_files and not specific_file_ids:
457
585
            # All files are unversioned, so just return an empty delta
458
586
            # _compare_trees would think we want a complete delta
459
 
            return delta.TreeDelta()
 
587
            result = delta.TreeDelta()
 
588
            fake_entry = InventoryFile('unused', 'unused', 'unused')
 
589
            result.unversioned = [(path, None,
 
590
                self.target._comparison_data(fake_entry, path)[0]) for path in
 
591
                specific_files]
 
592
            return result
460
593
        return delta._compare_trees(self.source, self.target, want_unchanged,
461
 
            specific_file_ids, include_root)
 
594
            specific_files, include_root, extra_trees=extra_trees,
 
595
            want_unversioned=want_unversioned)
462
596
 
463
 
    def _iter_changes(self, from_tree, to_tree, include_unchanged, 
464
 
                      specific_file_ids, pb):
 
597
    def _iter_changes(self, include_unchanged=False,
 
598
                      specific_files=None, pb=None, extra_trees=[],
 
599
                      require_versioned=True, want_unversioned=False):
465
600
        """Generate an iterator of changes between trees.
466
601
 
467
602
        A tuple is returned:
468
 
        (file_id, path, changed_content, versioned, parent, name, kind,
 
603
        (file_id, (path_in_source, path_in_target),
 
604
         changed_content, versioned, parent, name, kind,
469
605
         executable)
470
606
 
471
 
        Path is relative to the to_tree.  changed_content is True if the file's
472
 
        content has changed.  This includes changes to its kind, and to
473
 
        a symlink's target.
 
607
        Changed_content is True if the file's content has changed.  This
 
608
        includes changes to its kind, and to a symlink's target.
474
609
 
475
610
        versioned, parent, name, kind, executable are tuples of (from, to).
476
611
        If a file is missing in a tree, its kind is None.
477
612
 
478
 
        Iteration is done in parent-to-child order, relative to the to_tree.
 
613
        Iteration is done in parent-to-child order, relative to the target
 
614
        tree.
 
615
 
 
616
        There is no guarantee that all paths are in sorted order: the
 
617
        requirement to expand the search due to renames may result in children
 
618
        that should be found early being found late in the search, after
 
619
        lexically later results have been returned.
 
620
        :param require_versioned: Raise errors.PathsNotVersionedError if a
 
621
            path in the specific_files list is not versioned in one of
 
622
            source, target or extra_trees.
 
623
        :param want_unversioned: Should unversioned files be returned in the
 
624
            output. An unversioned file is defined as one with (False, False)
 
625
            for the versioned pair.
479
626
        """
 
627
        result = []
 
628
        lookup_trees = [self.source]
 
629
        if extra_trees:
 
630
             lookup_trees.extend(extra_trees)
 
631
        specific_file_ids = self.target.paths2ids(specific_files,
 
632
            lookup_trees, require_versioned=require_versioned)
 
633
        if want_unversioned:
 
634
            all_unversioned = sorted([(p.split('/'), p) for p in self.target.extras()
 
635
                if not specific_files or
 
636
                    osutils.is_inside_any(specific_files, p)])
 
637
            all_unversioned = deque(all_unversioned)
 
638
        else:
 
639
            all_unversioned = deque()
480
640
        to_paths = {}
481
 
        from_entries_by_dir = list(from_tree.inventory.iter_entries_by_dir(
 
641
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
482
642
            specific_file_ids=specific_file_ids))
483
643
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
484
 
        to_entries_by_dir = list(to_tree.inventory.iter_entries_by_dir(
 
644
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
485
645
            specific_file_ids=specific_file_ids))
486
646
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
487
647
        entry_count = 0
 
648
        # the unversioned path lookup only occurs on real trees - where there 
 
649
        # can be extras. So the fake_entry is solely used to look up
 
650
        # executable it values when execute is not supported.
 
651
        fake_entry = InventoryFile('unused', 'unused', 'unused')
488
652
        for to_path, to_entry in to_entries_by_dir:
 
653
            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
 
654
                unversioned_path = all_unversioned.popleft()
 
655
                to_kind, to_executable, to_stat = \
 
656
                    self.target._comparison_data(fake_entry, unversioned_path[1])
 
657
                yield (None, (None, unversioned_path[1]), True, (False, False),
 
658
                    (None, None),
 
659
                    (None, unversioned_path[0][-1]),
 
660
                    (None, to_kind),
 
661
                    (None, to_executable))
489
662
            file_id = to_entry.file_id
490
663
            to_paths[file_id] = to_path
491
664
            entry_count += 1
497
670
                from_name = from_entry.name
498
671
                from_parent = from_entry.parent_id
499
672
                from_kind, from_executable, from_stat = \
500
 
                    from_tree._comparison_data(from_entry, from_path)
 
673
                    self.source._comparison_data(from_entry, from_path)
501
674
                entry_count += 1
502
675
            else:
503
676
                from_versioned = False
507
680
                from_executable = None
508
681
            versioned = (from_versioned, True)
509
682
            to_kind, to_executable, to_stat = \
510
 
                to_tree._comparison_data(to_entry, to_path)
 
683
                self.target._comparison_data(to_entry, to_path)
511
684
            kind = (from_kind, to_kind)
512
685
            if kind[0] != kind[1]:
513
686
                changed_content = True
514
687
            elif from_kind == 'file':
515
 
                from_size = from_tree._file_size(from_entry, from_stat)
516
 
                to_size = to_tree._file_size(to_entry, to_stat)
 
688
                from_size = self.source._file_size(from_entry, from_stat)
 
689
                to_size = self.target._file_size(to_entry, to_stat)
517
690
                if from_size != to_size:
518
691
                    changed_content = True
519
 
                elif (from_tree.get_file_sha1(file_id, from_path, from_stat) !=
520
 
                    to_tree.get_file_sha1(file_id, to_path, to_stat)):
 
692
                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
 
693
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
521
694
                    changed_content = True
522
695
            elif from_kind == 'symlink':
523
 
                if (from_tree.get_symlink_target(file_id) != 
524
 
                    to_tree.get_symlink_target(file_id)):
 
696
                if (self.source.get_symlink_target(file_id) !=
 
697
                    self.target.get_symlink_target(file_id)):
525
698
                    changed_content = True
 
699
                elif from_kind == 'tree-reference':
 
700
                    if (self.source.get_reference_revision(file_id, from_path)
 
701
                        != self.target.get_reference_revision(file_id, to_path)):
 
702
                        changed_content = True 
526
703
            parent = (from_parent, to_entry.parent_id)
527
704
            name = (from_name, to_entry.name)
528
705
            executable = (from_executable, to_executable)
529
706
            if pb is not None:
530
707
                pb.update('comparing files', entry_count, num_entries)
531
 
            if (changed_content is not False or versioned[0] != versioned[1] 
 
708
            if (changed_content is not False or versioned[0] != versioned[1]
532
709
                or parent[0] != parent[1] or name[0] != name[1] or 
533
710
                executable[0] != executable[1] or include_unchanged):
534
 
                yield (file_id, to_path, changed_content, versioned, parent,
535
 
                       name, kind, executable)
536
 
 
537
 
        def get_to_path(from_entry):
538
 
            if from_entry.parent_id is None:
539
 
                to_path = ''
 
711
                yield (file_id, (from_path, to_path), changed_content,
 
712
                    versioned, parent, name, kind, executable)
 
713
 
 
714
        while all_unversioned:
 
715
            # yield any trailing unversioned paths
 
716
            unversioned_path = all_unversioned.popleft()
 
717
            to_kind, to_executable, to_stat = \
 
718
                self.target._comparison_data(fake_entry, unversioned_path[1])
 
719
            yield (None, (None, unversioned_path[1]), True, (False, False),
 
720
                (None, None),
 
721
                (None, unversioned_path[0][-1]),
 
722
                (None, to_kind),
 
723
                (None, to_executable))
 
724
 
 
725
        def get_to_path(to_entry):
 
726
            if to_entry.parent_id is None:
 
727
                to_path = '' # the root
540
728
            else:
541
 
                if from_entry.parent_id not in to_paths:
542
 
                    get_to_path(from_tree.inventory[from_entry.parent_id])
543
 
                to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
544
 
                                           from_entry.name)
545
 
            to_paths[from_entry.file_id] = to_path
 
729
                if to_entry.parent_id not in to_paths:
 
730
                    # recurse up
 
731
                    return get_to_path(self.target.inventory[to_entry.parent_id])
 
732
                to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
 
733
                                           to_entry.name)
 
734
            to_paths[to_entry.file_id] = to_path
546
735
            return to_path
547
736
 
548
737
        for path, from_entry in from_entries_by_dir:
549
738
            file_id = from_entry.file_id
550
739
            if file_id in to_paths:
 
740
                # already returned
551
741
                continue
552
 
            to_path = get_to_path(from_entry)
 
742
            if not file_id in self.target.inventory:
 
743
                # common case - paths we have not emitted are not present in
 
744
                # target.
 
745
                to_path = None
 
746
            else:
 
747
                to_path = get_to_path(self.target.inventory[file_id])
553
748
            entry_count += 1
554
749
            if pb is not None:
555
750
                pb.update('comparing files', entry_count, num_entries)
557
752
            parent = (from_entry.parent_id, None)
558
753
            name = (from_entry.name, None)
559
754
            from_kind, from_executable, stat_value = \
560
 
                from_tree._comparison_data(from_entry, path)
 
755
                self.source._comparison_data(from_entry, path)
561
756
            kind = (from_kind, None)
562
757
            executable = (from_executable, None)
563
758
            changed_content = True
564
759
            # the parent's path is necessarily known at this point.
565
 
            yield(file_id, to_path, changed_content, versioned, parent,
 
760
            yield(file_id, (path, to_path), changed_content, versioned, parent,
566
761
                  name, kind, executable)
567
762
 
568
763