~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Vincent Ladeuil
  • Date: 2012-02-14 17:22:37 UTC
  • mfrom: (6466 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120214172237-7dv7er3n4uy8d5m4
Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
import os
18
20
import errno
19
21
from stat import S_ISREG, S_IEXEC
20
22
import time
21
23
 
22
 
import bzrlib
23
24
from bzrlib import (
 
25
    config as _mod_config,
24
26
    errors,
25
27
    lazy_import,
26
28
    registry,
 
29
    trace,
27
30
    tree,
28
31
    )
29
32
lazy_import.lazy_import(globals(), """
30
33
from bzrlib import (
31
34
    annotate,
32
35
    bencode,
33
 
    bzrdir,
 
36
    controldir,
34
37
    commit,
 
38
    conflicts,
35
39
    delta,
36
 
    errors,
37
40
    inventory,
38
41
    multiparent,
39
42
    osutils,
40
43
    revision as _mod_revision,
41
 
    trace,
42
44
    ui,
43
45
    urlutils,
44
46
    )
 
47
from bzrlib.i18n import gettext
45
48
""")
46
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
49
from bzrlib.errors import (DuplicateKey, MalformedTransform,
47
50
                           ReusingTransform, CantMoveRoot,
48
 
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
 
51
                           ImmortalLimbo, NoFinalPath,
49
52
                           UnableCreateSymlink)
50
53
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
51
 
from bzrlib.inventory import InventoryEntry
 
54
from bzrlib.mutabletree import MutableTree
52
55
from bzrlib.osutils import (
53
56
    delete_any,
54
57
    file_kind,
56
59
    pathjoin,
57
60
    sha_file,
58
61
    splitpath,
59
 
    supports_executable,
60
62
    )
61
63
from bzrlib.progress import ProgressPhase
62
64
from bzrlib.symbol_versioning import (
64
66
    deprecated_in,
65
67
    deprecated_method,
66
68
    )
67
 
from bzrlib.trace import warning
68
69
 
69
70
 
70
71
ROOT_PARENT = "root-parent"
105
106
        self._new_parent = {}
106
107
        # mapping of trans_id with new contents -> new file_kind
107
108
        self._new_contents = {}
 
109
        # mapping of trans_id => (sha1 of content, stat_value)
 
110
        self._observed_sha1s = {}
108
111
        # Set of trans_ids whose contents will be removed
109
112
        self._removed_contents = set()
110
113
        # Mapping of trans_id -> new execute-bit value
138
141
        # A counter of how many files have been renamed
139
142
        self.rename_count = 0
140
143
 
 
144
    def __enter__(self):
 
145
        """Support Context Manager API."""
 
146
        return self
 
147
 
 
148
    def __exit__(self, exc_type, exc_val, exc_tb):
 
149
        """Support Context Manager API."""
 
150
        self.finalize()
 
151
 
141
152
    def finalize(self):
142
153
        """Release the working tree lock, if held.
143
154
 
146
157
        """
147
158
        if self._tree is None:
148
159
            return
 
160
        for hook in MutableTree.hooks['post_transform']:
 
161
            hook(self._tree, self)
149
162
        self._tree.unlock()
150
163
        self._tree = None
151
164
 
218
231
        This means that the old root trans-id becomes obsolete, so it is
219
232
        recommended only to invoke this after the root trans-id has become
220
233
        irrelevant.
 
234
 
221
235
        """
222
 
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
236
        new_roots = [k for k, v in self._new_parent.iteritems() if v ==
223
237
                     ROOT_PARENT]
224
238
        if len(new_roots) < 1:
225
239
            return
229
243
            self._new_root = new_roots[0]
230
244
            return
231
245
        old_new_root = new_roots[0]
232
 
        # TODO: What to do if a old_new_root is present, but self._new_root is
233
 
        #       not listed as being removed? This code explicitly unversions
234
 
        #       the old root and versions it with the new file_id. Though that
235
 
        #       seems like an incomplete delta
236
 
 
237
246
        # unversion the new root's directory.
238
 
        file_id = self.final_file_id(old_new_root)
 
247
        if self.final_kind(self._new_root) is None:
 
248
            file_id = self.final_file_id(old_new_root)
 
249
        else:
 
250
            file_id = self.final_file_id(self._new_root)
239
251
        if old_new_root in self._new_id:
240
252
            self.cancel_versioning(old_new_root)
241
253
        else:
245
257
        if (self.tree_file_id(self._new_root) is not None and
246
258
            self._new_root not in self._removed_id):
247
259
            self.unversion_file(self._new_root)
248
 
        self.version_file(file_id, self._new_root)
 
260
        if file_id is not None:
 
261
            self.version_file(file_id, self._new_root)
249
262
 
250
263
        # Now move children of new root into old root directory.
251
264
        # Ensure all children are registered with the transaction, but don't
385
398
        return sorted(FinalPaths(self).get_paths(new_ids))
386
399
 
387
400
    def _inventory_altered(self):
388
 
        """Get the trans_ids and paths of files needing new inv entries."""
389
 
        new_ids = set()
390
 
        for id_set in [self._new_name, self._new_parent, self._new_id,
 
401
        """Determine which trans_ids need new Inventory entries.
 
402
 
 
403
        An new entry is needed when anything that would be reflected by an
 
404
        inventory entry changes, including file name, file_id, parent file_id,
 
405
        file kind, and the execute bit.
 
406
 
 
407
        Some care is taken to return entries with real changes, not cases
 
408
        where the value is deleted and then restored to its original value,
 
409
        but some actually unchanged values may be returned.
 
410
 
 
411
        :returns: A list of (path, trans_id) for all items requiring an
 
412
            inventory change. Ordered by path.
 
413
        """
 
414
        changed_ids = set()
 
415
        # Find entries whose file_ids are new (or changed).
 
416
        new_file_id = set(t for t in self._new_id
 
417
                          if self._new_id[t] != self.tree_file_id(t))
 
418
        for id_set in [self._new_name, self._new_parent, new_file_id,
391
419
                       self._new_executability]:
392
 
            new_ids.update(id_set)
 
420
            changed_ids.update(id_set)
 
421
        # removing implies a kind change
393
422
        changed_kind = set(self._removed_contents)
 
423
        # so does adding
394
424
        changed_kind.intersection_update(self._new_contents)
395
 
        changed_kind.difference_update(new_ids)
 
425
        # Ignore entries that are already known to have changed.
 
426
        changed_kind.difference_update(changed_ids)
 
427
        #  to keep only the truly changed ones
396
428
        changed_kind = (t for t in changed_kind
397
429
                        if self.tree_kind(t) != self.final_kind(t))
398
 
        new_ids.update(changed_kind)
399
 
        return sorted(FinalPaths(self).get_paths(new_ids))
 
430
        # all kind changes will alter the inventory
 
431
        changed_ids.update(changed_kind)
 
432
        # To find entries with changed parent_ids, find parents which existed,
 
433
        # but changed file_id.
 
434
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
 
435
        # Now add all their children to the set.
 
436
        for parent_trans_id in new_file_id:
 
437
            changed_ids.update(self.iter_tree_children(parent_trans_id))
 
438
        return sorted(FinalPaths(self).get_paths(changed_ids))
400
439
 
401
440
    def final_kind(self, trans_id):
402
441
        """Determine the final file kind, after any changes applied.
527
566
        for trans_id in self._removed_id:
528
567
            file_id = self.tree_file_id(trans_id)
529
568
            if file_id is not None:
530
 
                # XXX: This seems like something that should go via a different
531
 
                #      indirection.
532
 
                if self._tree.inventory[file_id].kind == 'directory':
 
569
                if self._tree.stored_kind(file_id) == 'directory':
533
570
                    parents.append(trans_id)
534
571
            elif self.tree_kind(trans_id) == 'directory':
535
572
                parents.append(trans_id)
592
629
        for trans_id in self._new_parent:
593
630
            seen = set()
594
631
            parent_id = trans_id
595
 
            while parent_id is not ROOT_PARENT:
 
632
            while parent_id != ROOT_PARENT:
596
633
                seen.add(parent_id)
597
634
                try:
598
635
                    parent_id = self.final_parent(parent_id)
608
645
        """If parent directories are versioned, children must be versioned."""
609
646
        conflicts = []
610
647
        for parent_id, children in by_parent.iteritems():
611
 
            if parent_id is ROOT_PARENT:
 
648
            if parent_id == ROOT_PARENT:
612
649
                continue
613
650
            if self.final_file_id(parent_id) is not None:
614
651
                continue
629
666
            if kind is None:
630
667
                conflicts.append(('versioning no contents', trans_id))
631
668
                continue
632
 
            if not InventoryEntry.versionable_kind(kind):
 
669
            if not inventory.InventoryEntry.versionable_kind(kind):
633
670
                conflicts.append(('versioning bad kind', trans_id, kind))
634
671
        return conflicts
635
672
 
707
744
        """Children must have a directory parent"""
708
745
        conflicts = []
709
746
        for parent_id, children in by_parent.iteritems():
710
 
            if parent_id is ROOT_PARENT:
 
747
            if parent_id == ROOT_PARENT:
711
748
                continue
712
749
            no_children = True
713
750
            for child_id in children:
729
766
 
730
767
    def _set_executability(self, path, trans_id):
731
768
        """Set the executability of versioned files """
732
 
        if supports_executable():
 
769
        if self._tree._supports_executable():
733
770
            new_executability = self._new_executability[trans_id]
734
771
            abspath = self._tree.abspath(path)
735
772
            current_mode = os.stat(abspath).st_mode
744
781
                    to_mode |= 0010 & ~umask
745
782
            else:
746
783
                to_mode = current_mode & ~0111
747
 
            os.chmod(abspath, to_mode)
 
784
            osutils.chmod_if_possible(abspath, to_mode)
748
785
 
749
786
    def _new_entry(self, name, parent_id, file_id):
750
787
        """Helper function to create a new filesystem entry."""
754
791
        return trans_id
755
792
 
756
793
    def new_file(self, name, parent_id, contents, file_id=None,
757
 
                 executable=None):
 
794
                 executable=None, sha1=None):
758
795
        """Convenience method to create files.
759
796
 
760
797
        name is the name of the file to create.
767
804
        trans_id = self._new_entry(name, parent_id, file_id)
768
805
        # TODO: rather than scheduling a set_executable call,
769
806
        # have create_file create the file with the right mode.
770
 
        self.create_file(contents, trans_id)
 
807
        self.create_file(contents, trans_id, sha1=sha1)
771
808
        if executable is not None:
772
809
            self.set_executability(executable, trans_id)
773
810
        return trans_id
1155
1192
        self._deletiondir = None
1156
1193
        # A mapping of transform ids to their limbo filename
1157
1194
        self._limbo_files = {}
 
1195
        self._possibly_stale_limbo_files = set()
1158
1196
        # A mapping of transform ids to a set of the transform ids of children
1159
1197
        # that their limbo directory has
1160
1198
        self._limbo_children = {}
1173
1211
        if self._tree is None:
1174
1212
            return
1175
1213
        try:
1176
 
            entries = [(self._limbo_name(t), t, k) for t, k in
1177
 
                       self._new_contents.iteritems()]
1178
 
            entries.sort(reverse=True)
1179
 
            for path, trans_id, kind in entries:
1180
 
                delete_any(path)
 
1214
            limbo_paths = self._limbo_files.values() + list(
 
1215
                self._possibly_stale_limbo_files)
 
1216
            limbo_paths = sorted(limbo_paths, reverse=True)
 
1217
            for path in limbo_paths:
 
1218
                try:
 
1219
                    delete_any(path)
 
1220
                except OSError, e:
 
1221
                    if e.errno != errno.ENOENT:
 
1222
                        raise
 
1223
                    # XXX: warn? perhaps we just got interrupted at an
 
1224
                    # inconvenient moment, but perhaps files are disappearing
 
1225
                    # from under us?
1181
1226
            try:
1182
1227
                delete_any(self._limbodir)
1183
1228
            except OSError:
1191
1236
        finally:
1192
1237
            TreeTransformBase.finalize(self)
1193
1238
 
 
1239
    def _limbo_supports_executable(self):
 
1240
        """Check if the limbo path supports the executable bit."""
 
1241
        # FIXME: Check actual file system capabilities of limbodir
 
1242
        return osutils.supports_executable()
 
1243
 
1194
1244
    def _limbo_name(self, trans_id):
1195
1245
        """Generate the limbo name of a file"""
1196
1246
        limbo_name = self._limbo_files.get(trans_id)
1232
1282
        entries from _limbo_files, because they are now stale.
1233
1283
        """
1234
1284
        for trans_id in trans_ids:
1235
 
            old_path = self._limbo_files.pop(trans_id)
 
1285
            old_path = self._limbo_files[trans_id]
 
1286
            self._possibly_stale_limbo_files.add(old_path)
 
1287
            del self._limbo_files[trans_id]
1236
1288
            if trans_id not in self._new_contents:
1237
1289
                continue
1238
1290
            new_path = self._limbo_name(trans_id)
1239
1291
            os.rename(old_path, new_path)
 
1292
            self._possibly_stale_limbo_files.remove(old_path)
1240
1293
            for descendant in self._limbo_descendants(trans_id):
1241
1294
                desc_path = self._limbo_files[descendant]
1242
1295
                desc_path = new_path + desc_path[len(old_path):]
1249
1302
            descendants.update(self._limbo_descendants(descendant))
1250
1303
        return descendants
1251
1304
 
1252
 
    def create_file(self, contents, trans_id, mode_id=None):
 
1305
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1253
1306
        """Schedule creation of a new file.
1254
1307
 
1255
 
        See also new_file.
1256
 
 
1257
 
        Contents is an iterator of strings, all of which will be written
1258
 
        to the target destination.
1259
 
 
1260
 
        New file takes the permissions of any existing file with that id,
1261
 
        unless mode_id is specified.
 
1308
        :seealso: new_file.
 
1309
 
 
1310
        :param contents: an iterator of strings, all of which will be written
 
1311
            to the target destination.
 
1312
        :param trans_id: TreeTransform handle
 
1313
        :param mode_id: If not None, force the mode of the target file to match
 
1314
            the mode of the object referenced by mode_id.
 
1315
            Otherwise, we will try to preserve mode bits of an existing file.
 
1316
        :param sha1: If the sha1 of this content is already known, pass it in.
 
1317
            We can use it to prevent future sha1 computations.
1262
1318
        """
1263
1319
        name = self._limbo_name(trans_id)
1264
1320
        f = open(name, 'wb')
1265
1321
        try:
1266
 
            try:
1267
 
                unique_add(self._new_contents, trans_id, 'file')
1268
 
            except:
1269
 
                # Clean up the file, it never got registered so
1270
 
                # TreeTransform.finalize() won't clean it up.
1271
 
                f.close()
1272
 
                os.unlink(name)
1273
 
                raise
1274
 
 
 
1322
            unique_add(self._new_contents, trans_id, 'file')
1275
1323
            f.writelines(contents)
1276
1324
        finally:
1277
1325
            f.close()
1278
1326
        self._set_mtime(name)
1279
1327
        self._set_mode(trans_id, mode_id, S_ISREG)
 
1328
        # It is unfortunate we have to use lstat instead of fstat, but we just
 
1329
        # used utime and chmod on the file, so we need the accurate final
 
1330
        # details.
 
1331
        if sha1 is not None:
 
1332
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1280
1333
 
1281
1334
    def _read_file_chunks(self, trans_id):
1282
1335
        cur_file = open(self._limbo_name(trans_id), 'rb')
1341
1394
    def cancel_creation(self, trans_id):
1342
1395
        """Cancel the creation of new file contents."""
1343
1396
        del self._new_contents[trans_id]
 
1397
        if trans_id in self._observed_sha1s:
 
1398
            del self._observed_sha1s[trans_id]
1344
1399
        children = self._limbo_children.get(trans_id)
1345
1400
        # if this is a limbo directory with children, move them before removing
1346
1401
        # the directory
1351
1406
        delete_any(self._limbo_name(trans_id))
1352
1407
 
1353
1408
    def new_orphan(self, trans_id, parent_id):
1354
 
        # FIXME: There is no tree config, so we use the branch one (it's weird
1355
 
        # to define it this way as orphaning can only occur in a working tree,
1356
 
        # but that's all we have (for now). It will find the option in
1357
 
        # locations.conf or bazaar.conf though) -- vila 20100916
1358
 
        conf = self._tree.branch.get_config()
1359
 
        conf_var_name = 'bzr.transform.orphan_policy'
1360
 
        orphan_policy = conf.get_user_option(conf_var_name)
1361
 
        default_policy = orphaning_registry.default_key
1362
 
        if orphan_policy is None:
1363
 
            orphan_policy = default_policy
1364
 
        if orphan_policy not in orphaning_registry:
1365
 
            trace.warning('%s (from %s) is not a known policy, defaulting to %s'
1366
 
                          % (orphan_policy, conf_var_name, default_policy))
1367
 
            orphan_policy = default_policy
1368
 
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1409
        conf = self._tree.get_config_stack()
 
1410
        handle_orphan = conf.get('bzr.transform.orphan_policy')
1369
1411
        handle_orphan(self, trans_id, parent_id)
1370
1412
 
1371
1413
 
1434
1476
orphaning_registry._set_default_key('conflict')
1435
1477
 
1436
1478
 
 
1479
opt_transform_orphan = _mod_config.RegistryOption(
 
1480
    'bzr.transform.orphan_policy', orphaning_registry,
 
1481
    help='Policy for orphaned files during transform operations.',
 
1482
    invalid='warning')
 
1483
 
 
1484
 
1437
1485
class TreeTransform(DiskTreeTransform):
1438
1486
    """Represent a tree transformation.
1439
1487
 
1510
1558
        try:
1511
1559
            limbodir = urlutils.local_path_from_url(
1512
1560
                tree._transport.abspath('limbo'))
1513
 
            try:
1514
 
                os.mkdir(limbodir)
1515
 
            except OSError, e:
1516
 
                if e.errno == errno.EEXIST:
1517
 
                    raise ExistingLimbo(limbodir)
 
1561
            osutils.ensure_empty_directory_exists(
 
1562
                limbodir,
 
1563
                errors.ExistingLimbo)
1518
1564
            deletiondir = urlutils.local_path_from_url(
1519
1565
                tree._transport.abspath('pending-deletion'))
1520
 
            try:
1521
 
                os.mkdir(deletiondir)
1522
 
            except OSError, e:
1523
 
                if e.errno == errno.EEXIST:
1524
 
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1566
            osutils.ensure_empty_directory_exists(
 
1567
                deletiondir,
 
1568
                errors.ExistingPendingDeletion)
1525
1569
        except:
1526
1570
            tree.unlock()
1527
1571
            raise
1590
1634
            else:
1591
1635
                raise
1592
1636
        if typefunc(mode):
1593
 
            os.chmod(self._limbo_name(trans_id), mode)
 
1637
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1594
1638
 
1595
1639
    def iter_tree_children(self, parent_id):
1596
1640
        """Iterate through the entry's tree children, if any"""
1674
1718
            calculating one.
1675
1719
        :param _mover: Supply an alternate FileMover, for testing
1676
1720
        """
 
1721
        for hook in MutableTree.hooks['pre_transform']:
 
1722
            hook(self._tree, self)
1677
1723
        if not no_conflicts:
1678
1724
            self._check_malformed()
1679
1725
        child_pb = ui.ui_factory.nested_progress_bar()
1680
1726
        try:
1681
1727
            if precomputed_delta is None:
1682
 
                child_pb.update('Apply phase', 0, 2)
 
1728
                child_pb.update(gettext('Apply phase'), 0, 2)
1683
1729
                inventory_delta = self._generate_inventory_delta()
1684
1730
                offset = 1
1685
1731
            else:
1690
1736
            else:
1691
1737
                mover = _mover
1692
1738
            try:
1693
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1739
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1694
1740
                self._apply_removals(mover)
1695
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1741
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1696
1742
                modified_paths = self._apply_insertions(mover)
1697
1743
            except:
1698
1744
                mover.rollback()
1701
1747
                mover.apply_deletions()
1702
1748
        finally:
1703
1749
            child_pb.finished()
 
1750
        if self.final_file_id(self.root) is None:
 
1751
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1704
1752
        self._tree.apply_inventory_delta(inventory_delta)
 
1753
        self._apply_observed_sha1s()
1705
1754
        self._done = True
1706
1755
        self.finalize()
1707
1756
        return _TransformResults(modified_paths, self.rename_count)
1715
1764
        try:
1716
1765
            for num, trans_id in enumerate(self._removed_id):
1717
1766
                if (num % 10) == 0:
1718
 
                    child_pb.update('removing file', num, total_entries)
 
1767
                    child_pb.update(gettext('removing file'), num, total_entries)
1719
1768
                if trans_id == self._new_root:
1720
1769
                    file_id = self._tree.get_root_id()
1721
1770
                else:
1733
1782
            final_kinds = {}
1734
1783
            for num, (path, trans_id) in enumerate(new_paths):
1735
1784
                if (num % 10) == 0:
1736
 
                    child_pb.update('adding file',
 
1785
                    child_pb.update(gettext('adding file'),
1737
1786
                                    num + len(self._removed_id), total_entries)
1738
1787
                file_id = new_path_file_ids[trans_id]
1739
1788
                if file_id is None:
1779
1828
        tree_paths.sort(reverse=True)
1780
1829
        child_pb = ui.ui_factory.nested_progress_bar()
1781
1830
        try:
1782
 
            for num, data in enumerate(tree_paths):
1783
 
                path, trans_id = data
1784
 
                child_pb.update('removing file', num, len(tree_paths))
 
1831
            for num, (path, trans_id) in enumerate(tree_paths):
 
1832
                # do not attempt to move root into a subdirectory of itself.
 
1833
                if path == '':
 
1834
                    continue
 
1835
                child_pb.update(gettext('removing file'), num, len(tree_paths))
1785
1836
                full_path = self._tree.abspath(path)
1786
1837
                if trans_id in self._removed_contents:
1787
1838
                    delete_path = os.path.join(self._deletiondir, trans_id)
1816
1867
        try:
1817
1868
            for num, (path, trans_id) in enumerate(new_paths):
1818
1869
                if (num % 10) == 0:
1819
 
                    child_pb.update('adding file', num, len(new_paths))
 
1870
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1820
1871
                full_path = self._tree.abspath(path)
1821
1872
                if trans_id in self._needs_rename:
1822
1873
                    try:
1827
1878
                            raise
1828
1879
                    else:
1829
1880
                        self.rename_count += 1
 
1881
                    # TODO: if trans_id in self._observed_sha1s, we should
 
1882
                    #       re-stat the final target, since ctime will be
 
1883
                    #       updated by the change.
1830
1884
                if (trans_id in self._new_contents or
1831
1885
                    self.path_changed(trans_id)):
1832
1886
                    if trans_id in self._new_contents:
1833
1887
                        modified_paths.append(full_path)
1834
1888
                if trans_id in self._new_executability:
1835
1889
                    self._set_executability(path, trans_id)
 
1890
                if trans_id in self._observed_sha1s:
 
1891
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
 
1892
                    st = osutils.lstat(full_path)
 
1893
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1836
1894
        finally:
1837
1895
            child_pb.finished()
 
1896
        for path, trans_id in new_paths:
 
1897
            # new_paths includes stuff like workingtree conflicts. Only the
 
1898
            # stuff in new_contents actually comes from limbo.
 
1899
            if trans_id in self._limbo_files:
 
1900
                del self._limbo_files[trans_id]
1838
1901
        self._new_contents.clear()
1839
1902
        return modified_paths
1840
1903
 
 
1904
    def _apply_observed_sha1s(self):
 
1905
        """After we have finished renaming everything, update observed sha1s
 
1906
 
 
1907
        This has to be done after self._tree.apply_inventory_delta, otherwise
 
1908
        it doesn't know anything about the files we are updating. Also, we want
 
1909
        to do this as late as possible, so that most entries end up cached.
 
1910
        """
 
1911
        # TODO: this doesn't update the stat information for directories. So
 
1912
        #       the first 'bzr status' will still need to rewrite
 
1913
        #       .bzr/checkout/dirstate. However, we at least don't need to
 
1914
        #       re-read all of the files.
 
1915
        # TODO: If the operation took a while, we could do a time.sleep(3) here
 
1916
        #       to allow the clock to tick over and ensure we won't have any
 
1917
        #       problems. (we could observe start time, and finish time, and if
 
1918
        #       it is less than eg 10% overhead, add a sleep call.)
 
1919
        paths = FinalPaths(self)
 
1920
        for trans_id, observed in self._observed_sha1s.iteritems():
 
1921
            path = paths.get_path(trans_id)
 
1922
            # We could get the file_id, but dirstate prefers to use the path
 
1923
            # anyway, and it is 'cheaper' to determine.
 
1924
            # file_id = self._new_id[trans_id]
 
1925
            self._tree._observed_sha1(None, path, observed)
 
1926
 
1841
1927
 
1842
1928
class TransformPreview(DiskTreeTransform):
1843
1929
    """A TreeTransform for generating preview trees.
1859
1945
        path = self._tree_id_paths.get(trans_id)
1860
1946
        if path is None:
1861
1947
            return None
1862
 
        file_id = self._tree.path2id(path)
1863
 
        try:
1864
 
            return self._tree.kind(file_id)
1865
 
        except errors.NoSuchFile:
1866
 
            return None
 
1948
        kind = self._tree.path_content_summary(path)[0]
 
1949
        if kind == 'missing':
 
1950
            kind = None
 
1951
        return kind
1867
1952
 
1868
1953
    def _set_mode(self, trans_id, mode_id, typefunc):
1869
1954
        """Set the mode of new file contents.
1893
1978
        raise NotImplementedError(self.new_orphan)
1894
1979
 
1895
1980
 
1896
 
class _PreviewTree(tree.Tree):
 
1981
class _PreviewTree(tree.InventoryTree):
1897
1982
    """Partial implementation of Tree to support show_diff_trees"""
1898
1983
 
1899
1984
    def __init__(self, transform):
1928
2013
                yield self._get_repository().revision_tree(revision_id)
1929
2014
 
1930
2015
    def _get_file_revision(self, file_id, vf, tree_revision):
1931
 
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
2016
        parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
1932
2017
                       self._iter_parent_trees()]
1933
2018
        vf.add_lines((file_id, tree_revision), parent_keys,
1934
2019
                     self.get_file_lines(file_id))
1938
2023
            vf.fallback_versionedfiles.append(base_vf)
1939
2024
        return tree_revision
1940
2025
 
1941
 
    def _stat_limbo_file(self, file_id):
1942
 
        trans_id = self._transform.trans_id_file_id(file_id)
 
2026
    def _stat_limbo_file(self, file_id=None, trans_id=None):
 
2027
        if trans_id is None:
 
2028
            trans_id = self._transform.trans_id_file_id(file_id)
1943
2029
        name = self._transform._limbo_name(trans_id)
1944
2030
        return os.lstat(name)
1945
2031
 
1970
2056
        pass
1971
2057
 
1972
2058
    @property
 
2059
    @deprecated_method(deprecated_in((2, 5, 0)))
1973
2060
    def inventory(self):
1974
2061
        """This Tree does not use inventory as its backing data."""
1975
2062
        raise NotImplementedError(_PreviewTree.inventory)
1976
2063
 
 
2064
    @property
 
2065
    def root_inventory(self):
 
2066
        """This Tree does not use inventory as its backing data."""
 
2067
        raise NotImplementedError(_PreviewTree.root_inventory)
 
2068
 
1977
2069
    def get_root_id(self):
1978
2070
        return self._transform.final_file_id(self._transform.root)
1979
2071
 
2160
2252
 
2161
2253
    def get_file_size(self, file_id):
2162
2254
        """See Tree.get_file_size"""
 
2255
        trans_id = self._transform.trans_id_file_id(file_id)
 
2256
        kind = self._transform.final_kind(trans_id)
 
2257
        if kind != 'file':
 
2258
            return None
 
2259
        if trans_id in self._transform._new_contents:
 
2260
            return self._stat_limbo_file(trans_id=trans_id).st_size
2163
2261
        if self.kind(file_id) == 'file':
2164
2262
            return self._transform._tree.get_file_size(file_id)
2165
2263
        else:
2166
2264
            return None
2167
2265
 
 
2266
    def get_file_verifier(self, file_id, path=None, stat_value=None):
 
2267
        trans_id = self._transform.trans_id_file_id(file_id)
 
2268
        kind = self._transform._new_contents.get(trans_id)
 
2269
        if kind is None:
 
2270
            return self._transform._tree.get_file_verifier(file_id)
 
2271
        if kind == 'file':
 
2272
            fileobj = self.get_file(file_id)
 
2273
            try:
 
2274
                return ("SHA1", sha_file(fileobj))
 
2275
            finally:
 
2276
                fileobj.close()
 
2277
 
2168
2278
    def get_file_sha1(self, file_id, path=None, stat_value=None):
2169
2279
        trans_id = self._transform.trans_id_file_id(file_id)
2170
2280
        kind = self._transform._new_contents.get(trans_id)
2193
2303
            except errors.NoSuchId:
2194
2304
                return False
2195
2305
 
 
2306
    def has_filename(self, path):
 
2307
        trans_id = self._path2trans_id(path)
 
2308
        if trans_id in self._transform._new_contents:
 
2309
            return True
 
2310
        elif trans_id in self._transform._removed_contents:
 
2311
            return False
 
2312
        else:
 
2313
            return self._transform._tree.has_filename(path)
 
2314
 
2196
2315
    def path_content_summary(self, path):
2197
2316
        trans_id = self._path2trans_id(path)
2198
2317
        tt = self._transform
2211
2330
            if kind == 'file':
2212
2331
                statval = os.lstat(limbo_name)
2213
2332
                size = statval.st_size
2214
 
                if not supports_executable():
 
2333
                if not tt._limbo_supports_executable():
2215
2334
                    executable = False
2216
2335
                else:
2217
2336
                    executable = statval.st_mode & S_IEXEC
2286
2405
                                   self.get_file(file_id).readlines(),
2287
2406
                                   default_revision)
2288
2407
 
2289
 
    def get_symlink_target(self, file_id):
 
2408
    def get_symlink_target(self, file_id, path=None):
2290
2409
        """See Tree.get_symlink_target"""
2291
2410
        if not self._content_change(file_id):
2292
2411
            return self._transform._tree.get_symlink_target(file_id)
2432
2551
    file_trans_id = {}
2433
2552
    top_pb = ui.ui_factory.nested_progress_bar()
2434
2553
    pp = ProgressPhase("Build phase", 2, top_pb)
2435
 
    if tree.inventory.root is not None:
 
2554
    if tree.get_root_id() is not None:
2436
2555
        # This is kind of a hack: we should be altering the root
2437
2556
        # as part of the regular tree shape diff logic.
2438
2557
        # The conditional test here is to avoid doing an
2453
2572
        try:
2454
2573
            deferred_contents = []
2455
2574
            num = 0
2456
 
            total = len(tree.inventory)
 
2575
            total = len(tree.all_file_ids())
2457
2576
            if delta_from_tree:
2458
2577
                precomputed_delta = []
2459
2578
            else:
2468
2587
                for dir, files in wt.walkdirs():
2469
2588
                    existing_files.update(f[0] for f in files)
2470
2589
            for num, (tree_path, entry) in \
2471
 
                enumerate(tree.inventory.iter_entries_by_dir()):
2472
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
2590
                enumerate(tree.iter_entries_by_dir()):
 
2591
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2473
2592
                if entry.parent_id is None:
2474
2593
                    continue
2475
2594
                reparent = False
2481
2600
                    kind = file_kind(target_path)
2482
2601
                    if kind == "directory":
2483
2602
                        try:
2484
 
                            bzrdir.BzrDir.open(target_path)
 
2603
                            controldir.ControlDir.open(target_path)
2485
2604
                        except errors.NotBranchError:
2486
2605
                            pass
2487
2606
                        else:
2502
2621
                    executable = tree.is_executable(file_id, tree_path)
2503
2622
                    if executable:
2504
2623
                        tt.set_executability(executable, trans_id)
2505
 
                    trans_data = (trans_id, tree_path)
 
2624
                    trans_data = (trans_id, tree_path, entry.text_sha1)
2506
2625
                    deferred_contents.append((file_id, trans_data))
2507
2626
                else:
2508
2627
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2524
2643
            precomputed_delta = None
2525
2644
        conflicts = cook_conflicts(raw_conflicts, tt)
2526
2645
        for conflict in conflicts:
2527
 
            warning(conflict)
 
2646
            trace.warning(unicode(conflict))
2528
2647
        try:
2529
2648
            wt.add_conflicts(conflicts)
2530
2649
        except errors.UnsupportedOperation:
2553
2672
        unchanged = dict(unchanged)
2554
2673
        new_desired_files = []
2555
2674
        count = 0
2556
 
        for file_id, (trans_id, tree_path) in desired_files:
 
2675
        for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2557
2676
            accelerator_path = unchanged.get(file_id)
2558
2677
            if accelerator_path is None:
2559
 
                new_desired_files.append((file_id, (trans_id, tree_path)))
 
2678
                new_desired_files.append((file_id,
 
2679
                    (trans_id, tree_path, text_sha1)))
2560
2680
                continue
2561
 
            pb.update('Adding file contents', count + offset, total)
 
2681
            pb.update(gettext('Adding file contents'), count + offset, total)
2562
2682
            if hardlink:
2563
2683
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2564
2684
                                   trans_id)
2569
2689
                    contents = filtered_output_bytes(contents, filters,
2570
2690
                        ContentFilterContext(tree_path, tree))
2571
2691
                try:
2572
 
                    tt.create_file(contents, trans_id)
 
2692
                    tt.create_file(contents, trans_id, sha1=text_sha1)
2573
2693
                finally:
2574
2694
                    try:
2575
2695
                        contents.close()
2578
2698
                        pass
2579
2699
            count += 1
2580
2700
        offset += count
2581
 
    for count, ((trans_id, tree_path), contents) in enumerate(
 
2701
    for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2582
2702
            tree.iter_files_bytes(new_desired_files)):
2583
2703
        if wt.supports_content_filtering():
2584
2704
            filters = wt._content_filter_stack(tree_path)
2585
2705
            contents = filtered_output_bytes(contents, filters,
2586
2706
                ContentFilterContext(tree_path, tree))
2587
 
        tt.create_file(contents, trans_id)
2588
 
        pb.update('Adding file contents', count + offset, total)
 
2707
        tt.create_file(contents, trans_id, sha1=text_sha1)
 
2708
        pb.update(gettext('Adding file contents'), count + offset, total)
2589
2709
 
2590
2710
 
2591
2711
def _reparent_children(tt, old_parent, new_parent):
2723
2843
            return new_name
2724
2844
 
2725
2845
 
2726
 
def _entry_changes(file_id, entry, working_tree):
2727
 
    """Determine in which ways the inventory entry has changed.
2728
 
 
2729
 
    Returns booleans: has_contents, content_mod, meta_mod
2730
 
    has_contents means there are currently contents, but they differ
2731
 
    contents_mod means contents need to be modified
2732
 
    meta_mod means the metadata needs to be modified
2733
 
    """
2734
 
    cur_entry = working_tree.inventory[file_id]
2735
 
    try:
2736
 
        working_kind = working_tree.kind(file_id)
2737
 
        has_contents = True
2738
 
    except NoSuchFile:
2739
 
        has_contents = False
2740
 
        contents_mod = True
2741
 
        meta_mod = False
2742
 
    if has_contents is True:
2743
 
        if entry.kind != working_kind:
2744
 
            contents_mod, meta_mod = True, False
2745
 
        else:
2746
 
            cur_entry._read_tree_state(working_tree.id2path(file_id),
2747
 
                                       working_tree)
2748
 
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
2749
 
            cur_entry._forget_tree_state()
2750
 
    return has_contents, contents_mod, meta_mod
2751
 
 
2752
 
 
2753
2846
def revert(working_tree, target_tree, filenames, backups=False,
2754
2847
           pb=None, change_reporter=None):
2755
2848
    """Revert a working tree's contents to those of a target tree."""
2765
2858
                unversioned_filter=working_tree.is_ignored)
2766
2859
            delta.report_changes(tt.iter_changes(), change_reporter)
2767
2860
        for conflict in conflicts:
2768
 
            warning(conflict)
 
2861
            trace.warning(unicode(conflict))
2769
2862
        pp.next_phase()
2770
2863
        tt.apply()
2771
2864
        working_tree.set_merge_modified(merge_modified)
2802
2895
                 backups, merge_modified, basis_tree=None):
2803
2896
    if basis_tree is not None:
2804
2897
        basis_tree.lock_read()
2805
 
    change_list = target_tree.iter_changes(working_tree,
 
2898
    # We ask the working_tree for its changes relative to the target, rather
 
2899
    # than the target changes relative to the working tree. Because WT4 has an
 
2900
    # optimizer to compare itself to a target, but no optimizer for the
 
2901
    # reverse.
 
2902
    change_list = working_tree.iter_changes(target_tree,
2806
2903
        specific_files=specific_files, pb=pb)
2807
2904
    if target_tree.get_root_id() is None:
2808
2905
        skip_root = True
2812
2909
        deferred_files = []
2813
2910
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2814
2911
                kind, executable) in enumerate(change_list):
2815
 
            if skip_root and file_id[0] is not None and parent[0] is None:
 
2912
            target_path, wt_path = path
 
2913
            target_versioned, wt_versioned = versioned
 
2914
            target_parent, wt_parent = parent
 
2915
            target_name, wt_name = name
 
2916
            target_kind, wt_kind = kind
 
2917
            target_executable, wt_executable = executable
 
2918
            if skip_root and wt_parent is None:
2816
2919
                continue
2817
2920
            trans_id = tt.trans_id_file_id(file_id)
2818
2921
            mode_id = None
2819
2922
            if changed_content:
2820
2923
                keep_content = False
2821
 
                if kind[0] == 'file' and (backups or kind[1] is None):
 
2924
                if wt_kind == 'file' and (backups or target_kind is None):
2822
2925
                    wt_sha1 = working_tree.get_file_sha1(file_id)
2823
2926
                    if merge_modified.get(file_id) != wt_sha1:
2824
2927
                        # acquire the basis tree lazily to prevent the
2827
2930
                        if basis_tree is None:
2828
2931
                            basis_tree = working_tree.basis_tree()
2829
2932
                            basis_tree.lock_read()
2830
 
                        if file_id in basis_tree:
 
2933
                        if basis_tree.has_id(file_id):
2831
2934
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
2832
2935
                                keep_content = True
2833
 
                        elif kind[1] is None and not versioned[1]:
 
2936
                        elif target_kind is None and not target_versioned:
2834
2937
                            keep_content = True
2835
 
                if kind[0] is not None:
 
2938
                if wt_kind is not None:
2836
2939
                    if not keep_content:
2837
2940
                        tt.delete_contents(trans_id)
2838
 
                    elif kind[1] is not None:
2839
 
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
2941
                    elif target_kind is not None:
 
2942
                        parent_trans_id = tt.trans_id_file_id(wt_parent)
2840
2943
                        backup_name = tt._available_backup_name(
2841
 
                            name[0], parent_trans_id)
 
2944
                            wt_name, parent_trans_id)
2842
2945
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
2843
 
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
2844
 
                        if versioned == (True, True):
 
2946
                        new_trans_id = tt.create_path(wt_name, parent_trans_id)
 
2947
                        if wt_versioned and target_versioned:
2845
2948
                            tt.unversion_file(trans_id)
2846
2949
                            tt.version_file(file_id, new_trans_id)
2847
2950
                        # New contents should have the same unix perms as old
2848
2951
                        # contents
2849
2952
                        mode_id = trans_id
2850
2953
                        trans_id = new_trans_id
2851
 
                if kind[1] in ('directory', 'tree-reference'):
 
2954
                if target_kind in ('directory', 'tree-reference'):
2852
2955
                    tt.create_directory(trans_id)
2853
 
                    if kind[1] == 'tree-reference':
 
2956
                    if target_kind == 'tree-reference':
2854
2957
                        revision = target_tree.get_reference_revision(file_id,
2855
 
                                                                      path[1])
 
2958
                                                                      target_path)
2856
2959
                        tt.set_tree_reference(revision, trans_id)
2857
 
                elif kind[1] == 'symlink':
 
2960
                elif target_kind == 'symlink':
2858
2961
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2859
2962
                                      trans_id)
2860
 
                elif kind[1] == 'file':
 
2963
                elif target_kind == 'file':
2861
2964
                    deferred_files.append((file_id, (trans_id, mode_id)))
2862
2965
                    if basis_tree is None:
2863
2966
                        basis_tree = working_tree.basis_tree()
2864
2967
                        basis_tree.lock_read()
2865
2968
                    new_sha1 = target_tree.get_file_sha1(file_id)
2866
 
                    if (file_id in basis_tree and new_sha1 ==
2867
 
                        basis_tree.get_file_sha1(file_id)):
 
2969
                    if (basis_tree.has_id(file_id) and
 
2970
                        new_sha1 == basis_tree.get_file_sha1(file_id)):
2868
2971
                        if file_id in merge_modified:
2869
2972
                            del merge_modified[file_id]
2870
2973
                    else:
2871
2974
                        merge_modified[file_id] = new_sha1
2872
2975
 
2873
2976
                    # preserve the execute bit when backing up
2874
 
                    if keep_content and executable[0] == executable[1]:
2875
 
                        tt.set_executability(executable[1], trans_id)
2876
 
                elif kind[1] is not None:
2877
 
                    raise AssertionError(kind[1])
2878
 
            if versioned == (False, True):
 
2977
                    if keep_content and wt_executable == target_executable:
 
2978
                        tt.set_executability(target_executable, trans_id)
 
2979
                elif target_kind is not None:
 
2980
                    raise AssertionError(target_kind)
 
2981
            if not wt_versioned and target_versioned:
2879
2982
                tt.version_file(file_id, trans_id)
2880
 
            if versioned == (True, False):
 
2983
            if wt_versioned and not target_versioned:
2881
2984
                tt.unversion_file(trans_id)
2882
 
            if (name[1] is not None and
2883
 
                (name[0] != name[1] or parent[0] != parent[1])):
2884
 
                if name[1] == '' and parent[1] is None:
 
2985
            if (target_name is not None and
 
2986
                (wt_name != target_name or wt_parent != target_parent)):
 
2987
                if target_name == '' and target_parent is None:
2885
2988
                    parent_trans = ROOT_PARENT
2886
2989
                else:
2887
 
                    parent_trans = tt.trans_id_file_id(parent[1])
2888
 
                if parent[0] is None and versioned[0]:
2889
 
                    tt.adjust_root_path(name[1], parent_trans)
 
2990
                    parent_trans = tt.trans_id_file_id(target_parent)
 
2991
                if wt_parent is None and wt_versioned:
 
2992
                    tt.adjust_root_path(target_name, parent_trans)
2890
2993
                else:
2891
 
                    tt.adjust_path(name[1], parent_trans, trans_id)
2892
 
            if executable[0] != executable[1] and kind[1] == "file":
2893
 
                tt.set_executability(executable[1], trans_id)
 
2994
                    tt.adjust_path(target_name, parent_trans, trans_id)
 
2995
            if wt_executable != target_executable and target_kind == "file":
 
2996
                tt.set_executability(target_executable, trans_id)
2894
2997
        if working_tree.supports_content_filtering():
2895
2998
            for index, ((trans_id, mode_id), bytes) in enumerate(
2896
2999
                target_tree.iter_files_bytes(deferred_files)):
2922
3025
    pb = ui.ui_factory.nested_progress_bar()
2923
3026
    try:
2924
3027
        for n in range(10):
2925
 
            pb.update('Resolution pass', n+1, 10)
 
3028
            pb.update(gettext('Resolution pass'), n+1, 10)
2926
3029
            conflicts = tt.find_conflicts()
2927
3030
            if len(conflicts) == 0:
2928
3031
                return new_conflicts
2952
3055
                existing_file, new_file = conflict[2], conflict[1]
2953
3056
            else:
2954
3057
                existing_file, new_file = conflict[1], conflict[2]
2955
 
            new_name = tt.final_name(existing_file)+'.moved'
 
3058
            new_name = tt.final_name(existing_file) + '.moved'
2956
3059
            tt.adjust_path(new_name, final_parent, existing_file)
2957
3060
            new_conflicts.add((c_type, 'Moved existing file to',
2958
3061
                               existing_file, new_file))
2999
3102
                        file_id = tt.final_file_id(trans_id)
3000
3103
                        if file_id is None:
3001
3104
                            file_id = tt.inactive_file_id(trans_id)
3002
 
                        entry = path_tree.inventory[file_id]
 
3105
                        _, entry = path_tree.iter_entries_by_dir(
 
3106
                            [file_id]).next()
3003
3107
                        # special-case the other tree root (move its
3004
3108
                        # children to current root)
3005
3109
                        if entry.parent_id is None:
3020
3124
        elif c_type == 'unversioned parent':
3021
3125
            file_id = tt.inactive_file_id(conflict[1])
3022
3126
            # special-case the other tree root (move its children instead)
3023
 
            if path_tree and file_id in path_tree:
 
3127
            if path_tree and path_tree.has_id(file_id):
3024
3128
                if path_tree.path2id('') == file_id:
3025
3129
                    # This is the root entry, skip it
3026
3130
                    continue
3044
3148
 
3045
3149
def cook_conflicts(raw_conflicts, tt):
3046
3150
    """Generate a list of cooked conflicts, sorted by file path"""
3047
 
    from bzrlib.conflicts import Conflict
3048
3151
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3049
 
    return sorted(conflict_iter, key=Conflict.sort_key)
 
3152
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
3050
3153
 
3051
3154
 
3052
3155
def iter_cook_conflicts(raw_conflicts, tt):
3053
 
    from bzrlib.conflicts import Conflict
3054
3156
    fp = FinalPaths(tt)
3055
3157
    for conflict in raw_conflicts:
3056
3158
        c_type = conflict[0]
3058
3160
        modified_path = fp.get_path(conflict[2])
3059
3161
        modified_id = tt.final_file_id(conflict[2])
3060
3162
        if len(conflict) == 3:
3061
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
3062
 
                                     file_id=modified_id)
 
3163
            yield conflicts.Conflict.factory(
 
3164
                c_type, action=action, path=modified_path, file_id=modified_id)
3063
3165
 
3064
3166
        else:
3065
3167
            conflicting_path = fp.get_path(conflict[3])
3066
3168
            conflicting_id = tt.final_file_id(conflict[3])
3067
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
3068
 
                                   file_id=modified_id,
3069
 
                                   conflict_path=conflicting_path,
3070
 
                                   conflict_file_id=conflicting_id)
 
3169
            yield conflicts.Conflict.factory(
 
3170
                c_type, action=action, path=modified_path,
 
3171
                file_id=modified_id,
 
3172
                conflict_path=conflicting_path,
 
3173
                conflict_file_id=conflicting_id)
3071
3174
 
3072
3175
 
3073
3176
class _FileMover(object):