~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Martin Packman
  • Date: 2012-03-27 17:32:19 UTC
  • mto: (6437.54.3 2.5)
  • mto: This revision was merged to the branch mainline in revision 6525.
  • Revision ID: martin.packman@canonical.com-20120327173219-401pil42gke8j0xh
Fall back to sys.prefix not /usr when looking for .mo files

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
 
22
import time
20
23
 
21
 
from bzrlib.lazy_import import lazy_import
22
 
lazy_import(globals(), """
 
24
from bzrlib import (
 
25
    errors,
 
26
    lazy_import,
 
27
    registry,
 
28
    trace,
 
29
    tree,
 
30
    )
 
31
lazy_import.lazy_import(globals(), """
23
32
from bzrlib import (
24
33
    annotate,
25
34
    bencode,
26
 
    bzrdir,
 
35
    controldir,
 
36
    commit,
 
37
    conflicts,
27
38
    delta,
28
 
    errors,
29
39
    inventory,
30
40
    multiparent,
31
41
    osutils,
32
42
    revision as _mod_revision,
 
43
    ui,
 
44
    urlutils,
33
45
    )
 
46
from bzrlib.i18n import gettext
34
47
""")
35
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
 
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
48
from bzrlib.errors import (DuplicateKey, MalformedTransform,
 
49
                           ReusingTransform, CantMoveRoot,
37
50
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
51
                           UnableCreateSymlink)
39
52
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
40
 
from bzrlib.inventory import InventoryEntry
 
53
from bzrlib.mutabletree import MutableTree
41
54
from bzrlib.osutils import (
42
55
    delete_any,
43
56
    file_kind,
44
57
    has_symlinks,
45
 
    lexists,
46
58
    pathjoin,
47
59
    sha_file,
48
60
    splitpath,
49
 
    supports_executable,
50
 
)
51
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
61
    )
 
62
from bzrlib.progress import ProgressPhase
52
63
from bzrlib.symbol_versioning import (
53
 
        deprecated_function,
54
 
        deprecated_in,
55
 
        )
56
 
from bzrlib.trace import mutter, warning
57
 
from bzrlib import tree
58
 
import bzrlib.ui
59
 
import bzrlib.urlutils as urlutils
 
64
    deprecated_function,
 
65
    deprecated_in,
 
66
    deprecated_method,
 
67
    )
60
68
 
61
69
 
62
70
ROOT_PARENT = "root-parent"
63
71
 
64
 
 
65
72
def unique_add(map, key, value):
66
73
    if key in map:
67
74
        raise DuplicateKey(key=key)
68
75
    map[key] = value
69
76
 
70
77
 
 
78
 
71
79
class _TransformResults(object):
72
80
    def __init__(self, modified_paths, rename_count):
73
81
        object.__init__(self)
78
86
class TreeTransformBase(object):
79
87
    """The base class for TreeTransform and its kin."""
80
88
 
81
 
    def __init__(self, tree, pb=DummyProgress(),
 
89
    def __init__(self, tree, pb=None,
82
90
                 case_sensitive=True):
83
91
        """Constructor.
84
92
 
85
93
        :param tree: The tree that will be transformed, but not necessarily
86
94
            the output tree.
87
 
        :param pb: A ProgressBar indicating how much progress is being made
 
95
        :param pb: ignored
88
96
        :param case_sensitive: If True, the target of the transform is
89
97
            case sensitive, not just case preserving.
90
98
        """
97
105
        self._new_parent = {}
98
106
        # mapping of trans_id with new contents -> new file_kind
99
107
        self._new_contents = {}
 
108
        # mapping of trans_id => (sha1 of content, stat_value)
 
109
        self._observed_sha1s = {}
100
110
        # Set of trans_ids whose contents will be removed
101
111
        self._removed_contents = set()
102
112
        # Mapping of trans_id -> new execute-bit value
121
131
            self._new_root = self.trans_id_tree_file_id(root_id)
122
132
        else:
123
133
            self._new_root = None
124
 
        # Indictor of whether the transform has been applied
 
134
        # Indicator of whether the transform has been applied
125
135
        self._done = False
126
136
        # A progress bar
127
137
        self._pb = pb
130
140
        # A counter of how many files have been renamed
131
141
        self.rename_count = 0
132
142
 
 
143
    def __enter__(self):
 
144
        """Support Context Manager API."""
 
145
        return self
 
146
 
 
147
    def __exit__(self, exc_type, exc_val, exc_tb):
 
148
        """Support Context Manager API."""
 
149
        self.finalize()
 
150
 
133
151
    def finalize(self):
134
152
        """Release the working tree lock, if held.
135
153
 
138
156
        """
139
157
        if self._tree is None:
140
158
            return
 
159
        for hook in MutableTree.hooks['post_transform']:
 
160
            hook(self._tree, self)
141
161
        self._tree.unlock()
142
162
        self._tree = None
143
163
 
161
181
 
162
182
    def adjust_path(self, name, parent, trans_id):
163
183
        """Change the path that is assigned to a transaction id."""
 
184
        if parent is None:
 
185
            raise ValueError("Parent trans-id may not be None")
164
186
        if trans_id == self._new_root:
165
187
            raise CantMoveRoot
166
188
        self._new_name[trans_id] = name
167
189
        self._new_parent[trans_id] = parent
168
 
        if parent == ROOT_PARENT:
169
 
            if self._new_root is not None:
170
 
                raise ValueError("Cannot have multiple roots.")
171
 
            self._new_root = trans_id
172
190
 
173
191
    def adjust_root_path(self, name, parent):
174
192
        """Emulate moving the root by moving all children, instead.
202
220
        self.version_file(old_root_file_id, old_root)
203
221
        self.unversion_file(self._new_root)
204
222
 
 
223
    def fixup_new_roots(self):
 
224
        """Reinterpret requests to change the root directory
 
225
 
 
226
        Instead of creating a root directory, or moving an existing directory,
 
227
        all the attributes and children of the new root are applied to the
 
228
        existing root directory.
 
229
 
 
230
        This means that the old root trans-id becomes obsolete, so it is
 
231
        recommended only to invoke this after the root trans-id has become
 
232
        irrelevant.
 
233
 
 
234
        """
 
235
        new_roots = [k for k, v in self._new_parent.iteritems() if v ==
 
236
                     ROOT_PARENT]
 
237
        if len(new_roots) < 1:
 
238
            return
 
239
        if len(new_roots) != 1:
 
240
            raise ValueError('A tree cannot have two roots!')
 
241
        if self._new_root is None:
 
242
            self._new_root = new_roots[0]
 
243
            return
 
244
        old_new_root = new_roots[0]
 
245
        # unversion the new root's directory.
 
246
        if self.final_kind(self._new_root) is None:
 
247
            file_id = self.final_file_id(old_new_root)
 
248
        else:
 
249
            file_id = self.final_file_id(self._new_root)
 
250
        if old_new_root in self._new_id:
 
251
            self.cancel_versioning(old_new_root)
 
252
        else:
 
253
            self.unversion_file(old_new_root)
 
254
        # if, at this stage, root still has an old file_id, zap it so we can
 
255
        # stick a new one in.
 
256
        if (self.tree_file_id(self._new_root) is not None and
 
257
            self._new_root not in self._removed_id):
 
258
            self.unversion_file(self._new_root)
 
259
        if file_id is not None:
 
260
            self.version_file(file_id, self._new_root)
 
261
 
 
262
        # Now move children of new root into old root directory.
 
263
        # Ensure all children are registered with the transaction, but don't
 
264
        # use directly-- some tree children have new parents
 
265
        list(self.iter_tree_children(old_new_root))
 
266
        # Move all children of new root into old root directory.
 
267
        for child in self.by_parent().get(old_new_root, []):
 
268
            self.adjust_path(self.final_name(child), self._new_root, child)
 
269
 
 
270
        # Ensure old_new_root has no directory.
 
271
        if old_new_root in self._new_contents:
 
272
            self.cancel_creation(old_new_root)
 
273
        else:
 
274
            self.delete_contents(old_new_root)
 
275
 
 
276
        # prevent deletion of root directory.
 
277
        if self._new_root in self._removed_contents:
 
278
            self.cancel_deletion(self._new_root)
 
279
 
 
280
        # destroy path info for old_new_root.
 
281
        del self._new_parent[old_new_root]
 
282
        del self._new_name[old_new_root]
 
283
 
205
284
    def trans_id_tree_file_id(self, inventory_id):
206
285
        """Determine the transaction id of a working tree file.
207
286
 
253
332
 
254
333
    def delete_contents(self, trans_id):
255
334
        """Schedule the contents of a path entry for deletion"""
256
 
        self.tree_kind(trans_id)
257
 
        self._removed_contents.add(trans_id)
 
335
        kind = self.tree_kind(trans_id)
 
336
        if kind is not None:
 
337
            self._removed_contents.add(trans_id)
258
338
 
259
339
    def cancel_deletion(self, trans_id):
260
340
        """Cancel a scheduled deletion"""
317
397
        return sorted(FinalPaths(self).get_paths(new_ids))
318
398
 
319
399
    def _inventory_altered(self):
320
 
        """Get the trans_ids and paths of files needing new inv entries."""
321
 
        new_ids = set()
322
 
        for id_set in [self._new_name, self._new_parent, self._new_id,
 
400
        """Determine which trans_ids need new Inventory entries.
 
401
 
 
402
        An new entry is needed when anything that would be reflected by an
 
403
        inventory entry changes, including file name, file_id, parent file_id,
 
404
        file kind, and the execute bit.
 
405
 
 
406
        Some care is taken to return entries with real changes, not cases
 
407
        where the value is deleted and then restored to its original value,
 
408
        but some actually unchanged values may be returned.
 
409
 
 
410
        :returns: A list of (path, trans_id) for all items requiring an
 
411
            inventory change. Ordered by path.
 
412
        """
 
413
        changed_ids = set()
 
414
        # Find entries whose file_ids are new (or changed).
 
415
        new_file_id = set(t for t in self._new_id
 
416
                          if self._new_id[t] != self.tree_file_id(t))
 
417
        for id_set in [self._new_name, self._new_parent, new_file_id,
323
418
                       self._new_executability]:
324
 
            new_ids.update(id_set)
 
419
            changed_ids.update(id_set)
 
420
        # removing implies a kind change
325
421
        changed_kind = set(self._removed_contents)
 
422
        # so does adding
326
423
        changed_kind.intersection_update(self._new_contents)
327
 
        changed_kind.difference_update(new_ids)
328
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
329
 
                        self.final_kind(t))
330
 
        new_ids.update(changed_kind)
331
 
        return sorted(FinalPaths(self).get_paths(new_ids))
 
424
        # Ignore entries that are already known to have changed.
 
425
        changed_kind.difference_update(changed_ids)
 
426
        #  to keep only the truly changed ones
 
427
        changed_kind = (t for t in changed_kind
 
428
                        if self.tree_kind(t) != self.final_kind(t))
 
429
        # all kind changes will alter the inventory
 
430
        changed_ids.update(changed_kind)
 
431
        # To find entries with changed parent_ids, find parents which existed,
 
432
        # but changed file_id.
 
433
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
 
434
        # Now add all their children to the set.
 
435
        for parent_trans_id in new_file_id:
 
436
            changed_ids.update(self.iter_tree_children(parent_trans_id))
 
437
        return sorted(FinalPaths(self).get_paths(changed_ids))
332
438
 
333
439
    def final_kind(self, trans_id):
334
440
        """Determine the final file kind, after any changes applied.
335
441
 
336
 
        Raises NoSuchFile if the file does not exist/has no contents.
337
 
        (It is conceivable that a path would be created without the
338
 
        corresponding contents insertion command)
 
442
        :return: None if the file does not exist/has no contents.  (It is
 
443
            conceivable that a path would be created without the corresponding
 
444
            contents insertion command)
339
445
        """
340
446
        if trans_id in self._new_contents:
341
447
            return self._new_contents[trans_id]
342
448
        elif trans_id in self._removed_contents:
343
 
            raise NoSuchFile(None)
 
449
            return None
344
450
        else:
345
451
            return self.tree_kind(trans_id)
346
452
 
442
548
        conflicts.extend(self._overwrite_conflicts())
443
549
        return conflicts
444
550
 
 
551
    def _check_malformed(self):
 
552
        conflicts = self.find_conflicts()
 
553
        if len(conflicts) != 0:
 
554
            raise MalformedTransform(conflicts=conflicts)
 
555
 
445
556
    def _add_tree_children(self):
446
557
        """Add all the children of all active parents to the known paths.
447
558
 
454
565
        for trans_id in self._removed_id:
455
566
            file_id = self.tree_file_id(trans_id)
456
567
            if file_id is not None:
457
 
                if self._tree.inventory[file_id].kind == 'directory':
 
568
                if self._tree.stored_kind(file_id) == 'directory':
458
569
                    parents.append(trans_id)
459
570
            elif self.tree_kind(trans_id) == 'directory':
460
571
                parents.append(trans_id)
463
574
            # ensure that all children are registered with the transaction
464
575
            list(self.iter_tree_children(parent_id))
465
576
 
 
577
    @deprecated_method(deprecated_in((2, 3, 0)))
466
578
    def has_named_child(self, by_parent, parent_id, name):
467
 
        try:
468
 
            children = by_parent[parent_id]
469
 
        except KeyError:
470
 
            children = []
471
 
        for child in children:
 
579
        return self._has_named_child(
 
580
            name, parent_id, known_children=by_parent.get(parent_id, []))
 
581
 
 
582
    def _has_named_child(self, name, parent_id, known_children):
 
583
        """Does a parent already have a name child.
 
584
 
 
585
        :param name: The searched for name.
 
586
 
 
587
        :param parent_id: The parent for which the check is made.
 
588
 
 
589
        :param known_children: The already known children. This should have
 
590
            been recently obtained from `self.by_parent.get(parent_id)`
 
591
            (or will be if None is passed).
 
592
        """
 
593
        if known_children is None:
 
594
            known_children = self.by_parent().get(parent_id, [])
 
595
        for child in known_children:
472
596
            if self.final_name(child) == name:
473
597
                return True
474
 
        try:
475
 
            path = self._tree_id_paths[parent_id]
476
 
        except KeyError:
 
598
        parent_path = self._tree_id_paths.get(parent_id, None)
 
599
        if parent_path is None:
 
600
            # No parent... no children
477
601
            return False
478
 
        childpath = joinpath(path, name)
479
 
        child_id = self._tree_path_ids.get(childpath)
 
602
        child_path = joinpath(parent_path, name)
 
603
        child_id = self._tree_path_ids.get(child_path, None)
480
604
        if child_id is None:
481
 
            return lexists(self._tree.abspath(childpath))
 
605
            # Not known by the tree transform yet, check the filesystem
 
606
            return osutils.lexists(self._tree.abspath(child_path))
482
607
        else:
483
 
            if self.final_parent(child_id) != parent_id:
484
 
                return False
485
 
            if child_id in self._removed_contents:
486
 
                # XXX What about dangling file-ids?
487
 
                return False
488
 
            else:
489
 
                return True
 
608
            raise AssertionError('child_id is missing: %s, %s, %s'
 
609
                                 % (name, parent_id, child_id))
 
610
 
 
611
    def _available_backup_name(self, name, target_id):
 
612
        """Find an available backup name.
 
613
 
 
614
        :param name: The basename of the file.
 
615
 
 
616
        :param target_id: The directory trans_id where the backup should 
 
617
            be placed.
 
618
        """
 
619
        known_children = self.by_parent().get(target_id, [])
 
620
        return osutils.available_backup_name(
 
621
            name,
 
622
            lambda base: self._has_named_child(
 
623
                base, target_id, known_children))
490
624
 
491
625
    def _parent_loops(self):
492
626
        """No entry should be its own ancestor"""
494
628
        for trans_id in self._new_parent:
495
629
            seen = set()
496
630
            parent_id = trans_id
497
 
            while parent_id is not ROOT_PARENT:
 
631
            while parent_id != ROOT_PARENT:
498
632
                seen.add(parent_id)
499
633
                try:
500
634
                    parent_id = self.final_parent(parent_id)
510
644
        """If parent directories are versioned, children must be versioned."""
511
645
        conflicts = []
512
646
        for parent_id, children in by_parent.iteritems():
513
 
            if parent_id is ROOT_PARENT:
 
647
            if parent_id == ROOT_PARENT:
514
648
                continue
515
649
            if self.final_file_id(parent_id) is not None:
516
650
                continue
527
661
        """
528
662
        conflicts = []
529
663
        for trans_id in self._new_id.iterkeys():
530
 
            try:
531
 
                kind = self.final_kind(trans_id)
532
 
            except NoSuchFile:
 
664
            kind = self.final_kind(trans_id)
 
665
            if kind is None:
533
666
                conflicts.append(('versioning no contents', trans_id))
534
667
                continue
535
 
            if not InventoryEntry.versionable_kind(kind):
 
668
            if not inventory.InventoryEntry.versionable_kind(kind):
536
669
                conflicts.append(('versioning bad kind', trans_id, kind))
537
670
        return conflicts
538
671
 
549
682
            if self.final_file_id(trans_id) is None:
550
683
                conflicts.append(('unversioned executability', trans_id))
551
684
            else:
552
 
                try:
553
 
                    non_file = self.final_kind(trans_id) != "file"
554
 
                except NoSuchFile:
555
 
                    non_file = True
556
 
                if non_file is True:
 
685
                if self.final_kind(trans_id) != "file":
557
686
                    conflicts.append(('non-file executability', trans_id))
558
687
        return conflicts
559
688
 
561
690
        """Check for overwrites (not permitted on Win32)"""
562
691
        conflicts = []
563
692
        for trans_id in self._new_contents:
564
 
            try:
565
 
                self.tree_kind(trans_id)
566
 
            except NoSuchFile:
 
693
            if self.tree_kind(trans_id) is None:
567
694
                continue
568
695
            if trans_id not in self._removed_contents:
569
696
                conflicts.append(('overwrite', trans_id,
576
703
        if (self._new_name, self._new_parent) == ({}, {}):
577
704
            return conflicts
578
705
        for children in by_parent.itervalues():
579
 
            name_ids = [(self.final_name(t), t) for t in children]
580
 
            if not self._case_sensitive_target:
581
 
                name_ids = [(n.lower(), t) for n, t in name_ids]
 
706
            name_ids = []
 
707
            for child_tid in children:
 
708
                name = self.final_name(child_tid)
 
709
                if name is not None:
 
710
                    # Keep children only if they still exist in the end
 
711
                    if not self._case_sensitive_target:
 
712
                        name = name.lower()
 
713
                    name_ids.append((name, child_tid))
582
714
            name_ids.sort()
583
715
            last_name = None
584
716
            last_trans_id = None
585
717
            for name, trans_id in name_ids:
586
 
                try:
587
 
                    kind = self.final_kind(trans_id)
588
 
                except NoSuchFile:
589
 
                    kind = None
 
718
                kind = self.final_kind(trans_id)
590
719
                file_id = self.final_file_id(trans_id)
591
720
                if kind is None and file_id is None:
592
721
                    continue
611
740
        return conflicts
612
741
 
613
742
    def _parent_type_conflicts(self, by_parent):
614
 
        """parents must have directory 'contents'."""
 
743
        """Children must have a directory parent"""
615
744
        conflicts = []
616
745
        for parent_id, children in by_parent.iteritems():
617
 
            if parent_id is ROOT_PARENT:
618
 
                continue
619
 
            if not self._any_contents(children):
620
 
                continue
621
 
            for child in children:
622
 
                try:
623
 
                    self.final_kind(child)
624
 
                except NoSuchFile:
625
 
                    continue
626
 
            try:
627
 
                kind = self.final_kind(parent_id)
628
 
            except NoSuchFile:
629
 
                kind = None
 
746
            if parent_id == ROOT_PARENT:
 
747
                continue
 
748
            no_children = True
 
749
            for child_id in children:
 
750
                if self.final_kind(child_id) is not None:
 
751
                    no_children = False
 
752
                    break
 
753
            if no_children:
 
754
                continue
 
755
            # There is at least a child, so we need an existing directory to
 
756
            # contain it.
 
757
            kind = self.final_kind(parent_id)
630
758
            if kind is None:
 
759
                # The directory will be deleted
631
760
                conflicts.append(('missing parent', parent_id))
632
761
            elif kind != "directory":
 
762
                # Meh, we need a *directory* to put something in it
633
763
                conflicts.append(('non-directory parent', parent_id))
634
764
        return conflicts
635
765
 
636
 
    def _any_contents(self, trans_ids):
637
 
        """Return true if any of the trans_ids, will have contents."""
638
 
        for trans_id in trans_ids:
639
 
            try:
640
 
                kind = self.final_kind(trans_id)
641
 
            except NoSuchFile:
642
 
                continue
643
 
            return True
644
 
        return False
645
 
 
646
766
    def _set_executability(self, path, trans_id):
647
767
        """Set the executability of versioned files """
648
 
        if supports_executable():
 
768
        if self._tree._supports_executable():
649
769
            new_executability = self._new_executability[trans_id]
650
770
            abspath = self._tree.abspath(path)
651
771
            current_mode = os.stat(abspath).st_mode
660
780
                    to_mode |= 0010 & ~umask
661
781
            else:
662
782
                to_mode = current_mode & ~0111
663
 
            os.chmod(abspath, to_mode)
 
783
            osutils.chmod_if_possible(abspath, to_mode)
664
784
 
665
785
    def _new_entry(self, name, parent_id, file_id):
666
786
        """Helper function to create a new filesystem entry."""
670
790
        return trans_id
671
791
 
672
792
    def new_file(self, name, parent_id, contents, file_id=None,
673
 
                 executable=None):
 
793
                 executable=None, sha1=None):
674
794
        """Convenience method to create files.
675
795
 
676
796
        name is the name of the file to create.
683
803
        trans_id = self._new_entry(name, parent_id, file_id)
684
804
        # TODO: rather than scheduling a set_executable call,
685
805
        # have create_file create the file with the right mode.
686
 
        self.create_file(contents, trans_id)
 
806
        self.create_file(contents, trans_id, sha1=sha1)
687
807
        if executable is not None:
688
808
            self.set_executability(executable, trans_id)
689
809
        return trans_id
712
832
        self.create_symlink(target, trans_id)
713
833
        return trans_id
714
834
 
 
835
    def new_orphan(self, trans_id, parent_id):
 
836
        """Schedule an item to be orphaned.
 
837
 
 
838
        When a directory is about to be removed, its children, if they are not
 
839
        versioned are moved out of the way: they don't have a parent anymore.
 
840
 
 
841
        :param trans_id: The trans_id of the existing item.
 
842
        :param parent_id: The parent trans_id of the item.
 
843
        """
 
844
        raise NotImplementedError(self.new_orphan)
 
845
 
 
846
    def _get_potential_orphans(self, dir_id):
 
847
        """Find the potential orphans in a directory.
 
848
 
 
849
        A directory can't be safely deleted if there are versioned files in it.
 
850
        If all the contained files are unversioned then they can be orphaned.
 
851
 
 
852
        The 'None' return value means that the directory contains at least one
 
853
        versioned file and should not be deleted.
 
854
 
 
855
        :param dir_id: The directory trans id.
 
856
 
 
857
        :return: A list of the orphan trans ids or None if at least one
 
858
             versioned file is present.
 
859
        """
 
860
        orphans = []
 
861
        # Find the potential orphans, stop if one item should be kept
 
862
        for child_tid in self.by_parent()[dir_id]:
 
863
            if child_tid in self._removed_contents:
 
864
                # The child is removed as part of the transform. Since it was
 
865
                # versioned before, it's not an orphan
 
866
                continue
 
867
            elif self.final_file_id(child_tid) is None:
 
868
                # The child is not versioned
 
869
                orphans.append(child_tid)
 
870
            else:
 
871
                # We have a versioned file here, searching for orphans is
 
872
                # meaningless.
 
873
                orphans = None
 
874
                break
 
875
        return orphans
 
876
 
715
877
    def _affected_ids(self):
716
878
        """Return the set of transform ids affected by the transform"""
717
879
        trans_ids = set(self._removed_id)
776
938
        Return a (name, parent, kind, executable) tuple
777
939
        """
778
940
        to_name = self.final_name(to_trans_id)
779
 
        try:
780
 
            to_kind = self.final_kind(to_trans_id)
781
 
        except NoSuchFile:
782
 
            to_kind = None
 
941
        to_kind = self.final_kind(to_trans_id)
783
942
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
784
943
        if to_trans_id in self._new_executability:
785
944
            to_executable = self._new_executability[to_trans_id]
854
1013
    def get_preview_tree(self):
855
1014
        """Return a tree representing the result of the transform.
856
1015
 
857
 
        This tree only supports the subset of Tree functionality required
858
 
        by show_diff_trees.  It must only be compared to tt._tree.
 
1016
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
1017
        it.
859
1018
        """
860
1019
        return _PreviewTree(self)
861
1020
 
 
1021
    def commit(self, branch, message, merge_parents=None, strict=False,
 
1022
               timestamp=None, timezone=None, committer=None, authors=None,
 
1023
               revprops=None, revision_id=None):
 
1024
        """Commit the result of this TreeTransform to a branch.
 
1025
 
 
1026
        :param branch: The branch to commit to.
 
1027
        :param message: The message to attach to the commit.
 
1028
        :param merge_parents: Additional parent revision-ids specified by
 
1029
            pending merges.
 
1030
        :param strict: If True, abort the commit if there are unversioned
 
1031
            files.
 
1032
        :param timestamp: if not None, seconds-since-epoch for the time and
 
1033
            date.  (May be a float.)
 
1034
        :param timezone: Optional timezone for timestamp, as an offset in
 
1035
            seconds.
 
1036
        :param committer: Optional committer in email-id format.
 
1037
            (e.g. "J Random Hacker <jrandom@example.com>")
 
1038
        :param authors: Optional list of authors in email-id format.
 
1039
        :param revprops: Optional dictionary of revision properties.
 
1040
        :param revision_id: Optional revision id.  (Specifying a revision-id
 
1041
            may reduce performance for some non-native formats.)
 
1042
        :return: The revision_id of the revision committed.
 
1043
        """
 
1044
        self._check_malformed()
 
1045
        if strict:
 
1046
            unversioned = set(self._new_contents).difference(set(self._new_id))
 
1047
            for trans_id in unversioned:
 
1048
                if self.final_file_id(trans_id) is None:
 
1049
                    raise errors.StrictCommitFailed()
 
1050
 
 
1051
        revno, last_rev_id = branch.last_revision_info()
 
1052
        if last_rev_id == _mod_revision.NULL_REVISION:
 
1053
            if merge_parents is not None:
 
1054
                raise ValueError('Cannot supply merge parents for first'
 
1055
                                 ' commit.')
 
1056
            parent_ids = []
 
1057
        else:
 
1058
            parent_ids = [last_rev_id]
 
1059
            if merge_parents is not None:
 
1060
                parent_ids.extend(merge_parents)
 
1061
        if self._tree.get_revision_id() != last_rev_id:
 
1062
            raise ValueError('TreeTransform not based on branch basis: %s' %
 
1063
                             self._tree.get_revision_id())
 
1064
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
 
1065
        builder = branch.get_commit_builder(parent_ids,
 
1066
                                            timestamp=timestamp,
 
1067
                                            timezone=timezone,
 
1068
                                            committer=committer,
 
1069
                                            revprops=revprops,
 
1070
                                            revision_id=revision_id)
 
1071
        preview = self.get_preview_tree()
 
1072
        list(builder.record_iter_changes(preview, last_rev_id,
 
1073
                                         self.iter_changes()))
 
1074
        builder.finish_inventory()
 
1075
        revision_id = builder.commit(message)
 
1076
        branch.set_last_revision_info(revno + 1, revision_id)
 
1077
        return revision_id
 
1078
 
862
1079
    def _text_parent(self, trans_id):
863
1080
        file_id = self.tree_file_id(trans_id)
864
1081
        try:
958
1175
class DiskTreeTransform(TreeTransformBase):
959
1176
    """Tree transform storing its contents on disk."""
960
1177
 
961
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
1178
    def __init__(self, tree, limbodir, pb=None,
962
1179
                 case_sensitive=True):
963
1180
        """Constructor.
964
1181
        :param tree: The tree that will be transformed, but not necessarily
965
1182
            the output tree.
966
1183
        :param limbodir: A directory where new files can be stored until
967
1184
            they are installed in their proper places
968
 
        :param pb: A ProgressBar indicating how much progress is being made
 
1185
        :param pb: ignored
969
1186
        :param case_sensitive: If True, the target of the transform is
970
1187
            case sensitive, not just case preserving.
971
1188
        """
974
1191
        self._deletiondir = None
975
1192
        # A mapping of transform ids to their limbo filename
976
1193
        self._limbo_files = {}
 
1194
        self._possibly_stale_limbo_files = set()
977
1195
        # A mapping of transform ids to a set of the transform ids of children
978
1196
        # that their limbo directory has
979
1197
        self._limbo_children = {}
981
1199
        self._limbo_children_names = {}
982
1200
        # List of transform ids that need to be renamed from limbo into place
983
1201
        self._needs_rename = set()
 
1202
        self._creation_mtime = None
984
1203
 
985
1204
    def finalize(self):
986
1205
        """Release the working tree lock, if held, clean up limbo dir.
991
1210
        if self._tree is None:
992
1211
            return
993
1212
        try:
994
 
            entries = [(self._limbo_name(t), t, k) for t, k in
995
 
                       self._new_contents.iteritems()]
996
 
            entries.sort(reverse=True)
997
 
            for path, trans_id, kind in entries:
998
 
                if kind == "directory":
999
 
                    os.rmdir(path)
1000
 
                else:
1001
 
                    os.unlink(path)
 
1213
            limbo_paths = self._limbo_files.values() + list(
 
1214
                self._possibly_stale_limbo_files)
 
1215
            limbo_paths = sorted(limbo_paths, reverse=True)
 
1216
            for path in limbo_paths:
 
1217
                try:
 
1218
                    delete_any(path)
 
1219
                except OSError, e:
 
1220
                    if e.errno != errno.ENOENT:
 
1221
                        raise
 
1222
                    # XXX: warn? perhaps we just got interrupted at an
 
1223
                    # inconvenient moment, but perhaps files are disappearing
 
1224
                    # from under us?
1002
1225
            try:
1003
 
                os.rmdir(self._limbodir)
 
1226
                delete_any(self._limbodir)
1004
1227
            except OSError:
1005
1228
                # We don't especially care *why* the dir is immortal.
1006
1229
                raise ImmortalLimbo(self._limbodir)
1007
1230
            try:
1008
1231
                if self._deletiondir is not None:
1009
 
                    os.rmdir(self._deletiondir)
 
1232
                    delete_any(self._deletiondir)
1010
1233
            except OSError:
1011
1234
                raise errors.ImmortalPendingDeletion(self._deletiondir)
1012
1235
        finally:
1013
1236
            TreeTransformBase.finalize(self)
1014
1237
 
 
1238
    def _limbo_supports_executable(self):
 
1239
        """Check if the limbo path supports the executable bit."""
 
1240
        # FIXME: Check actual file system capabilities of limbodir
 
1241
        return osutils.supports_executable()
 
1242
 
1015
1243
    def _limbo_name(self, trans_id):
1016
1244
        """Generate the limbo name of a file"""
1017
1245
        limbo_name = self._limbo_files.get(trans_id)
1018
 
        if limbo_name is not None:
1019
 
            return limbo_name
1020
 
        parent = self._new_parent.get(trans_id)
1021
 
        # if the parent directory is already in limbo (e.g. when building a
1022
 
        # tree), choose a limbo name inside the parent, to reduce further
1023
 
        # renames.
1024
 
        use_direct_path = False
1025
 
        if self._new_contents.get(parent) == 'directory':
1026
 
            filename = self._new_name.get(trans_id)
1027
 
            if filename is not None:
1028
 
                if parent not in self._limbo_children:
1029
 
                    self._limbo_children[parent] = set()
1030
 
                    self._limbo_children_names[parent] = {}
1031
 
                    use_direct_path = True
1032
 
                # the direct path can only be used if no other file has
1033
 
                # already taken this pathname, i.e. if the name is unused, or
1034
 
                # if it is already associated with this trans_id.
1035
 
                elif self._case_sensitive_target:
1036
 
                    if (self._limbo_children_names[parent].get(filename)
1037
 
                        in (trans_id, None)):
1038
 
                        use_direct_path = True
1039
 
                else:
1040
 
                    for l_filename, l_trans_id in\
1041
 
                        self._limbo_children_names[parent].iteritems():
1042
 
                        if l_trans_id == trans_id:
1043
 
                            continue
1044
 
                        if l_filename.lower() == filename.lower():
1045
 
                            break
1046
 
                    else:
1047
 
                        use_direct_path = True
1048
 
 
1049
 
        if use_direct_path:
1050
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
 
            self._limbo_children[parent].add(trans_id)
1052
 
            self._limbo_children_names[parent][filename] = trans_id
1053
 
        else:
1054
 
            limbo_name = pathjoin(self._limbodir, trans_id)
1055
 
            self._needs_rename.add(trans_id)
1056
 
        self._limbo_files[trans_id] = limbo_name
 
1246
        if limbo_name is None:
 
1247
            limbo_name = self._generate_limbo_path(trans_id)
 
1248
            self._limbo_files[trans_id] = limbo_name
1057
1249
        return limbo_name
1058
1250
 
 
1251
    def _generate_limbo_path(self, trans_id):
 
1252
        """Generate a limbo path using the trans_id as the relative path.
 
1253
 
 
1254
        This is suitable as a fallback, and when the transform should not be
 
1255
        sensitive to the path encoding of the limbo directory.
 
1256
        """
 
1257
        self._needs_rename.add(trans_id)
 
1258
        return pathjoin(self._limbodir, trans_id)
 
1259
 
1059
1260
    def adjust_path(self, name, parent, trans_id):
1060
1261
        previous_parent = self._new_parent.get(trans_id)
1061
1262
        previous_name = self._new_name.get(trans_id)
1063
1264
        if (trans_id in self._limbo_files and
1064
1265
            trans_id not in self._needs_rename):
1065
1266
            self._rename_in_limbo([trans_id])
1066
 
            self._limbo_children[previous_parent].remove(trans_id)
1067
 
            del self._limbo_children_names[previous_parent][previous_name]
 
1267
            if previous_parent != parent:
 
1268
                self._limbo_children[previous_parent].remove(trans_id)
 
1269
            if previous_parent != parent or previous_name != name:
 
1270
                del self._limbo_children_names[previous_parent][previous_name]
1068
1271
 
1069
1272
    def _rename_in_limbo(self, trans_ids):
1070
1273
        """Fix limbo names so that the right final path is produced.
1078
1281
        entries from _limbo_files, because they are now stale.
1079
1282
        """
1080
1283
        for trans_id in trans_ids:
1081
 
            old_path = self._limbo_files.pop(trans_id)
 
1284
            old_path = self._limbo_files[trans_id]
 
1285
            self._possibly_stale_limbo_files.add(old_path)
 
1286
            del self._limbo_files[trans_id]
1082
1287
            if trans_id not in self._new_contents:
1083
1288
                continue
1084
1289
            new_path = self._limbo_name(trans_id)
1085
1290
            os.rename(old_path, new_path)
1086
 
 
1087
 
    def create_file(self, contents, trans_id, mode_id=None):
 
1291
            self._possibly_stale_limbo_files.remove(old_path)
 
1292
            for descendant in self._limbo_descendants(trans_id):
 
1293
                desc_path = self._limbo_files[descendant]
 
1294
                desc_path = new_path + desc_path[len(old_path):]
 
1295
                self._limbo_files[descendant] = desc_path
 
1296
 
 
1297
    def _limbo_descendants(self, trans_id):
 
1298
        """Return the set of trans_ids whose limbo paths descend from this."""
 
1299
        descendants = set(self._limbo_children.get(trans_id, []))
 
1300
        for descendant in list(descendants):
 
1301
            descendants.update(self._limbo_descendants(descendant))
 
1302
        return descendants
 
1303
 
 
1304
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1088
1305
        """Schedule creation of a new file.
1089
1306
 
1090
 
        See also new_file.
1091
 
 
1092
 
        Contents is an iterator of strings, all of which will be written
1093
 
        to the target destination.
1094
 
 
1095
 
        New file takes the permissions of any existing file with that id,
1096
 
        unless mode_id is specified.
 
1307
        :seealso: new_file.
 
1308
 
 
1309
        :param contents: an iterator of strings, all of which will be written
 
1310
            to the target destination.
 
1311
        :param trans_id: TreeTransform handle
 
1312
        :param mode_id: If not None, force the mode of the target file to match
 
1313
            the mode of the object referenced by mode_id.
 
1314
            Otherwise, we will try to preserve mode bits of an existing file.
 
1315
        :param sha1: If the sha1 of this content is already known, pass it in.
 
1316
            We can use it to prevent future sha1 computations.
1097
1317
        """
1098
1318
        name = self._limbo_name(trans_id)
1099
1319
        f = open(name, 'wb')
1100
1320
        try:
1101
 
            try:
1102
 
                unique_add(self._new_contents, trans_id, 'file')
1103
 
            except:
1104
 
                # Clean up the file, it never got registered so
1105
 
                # TreeTransform.finalize() won't clean it up.
1106
 
                f.close()
1107
 
                os.unlink(name)
1108
 
                raise
1109
 
 
 
1321
            unique_add(self._new_contents, trans_id, 'file')
1110
1322
            f.writelines(contents)
1111
1323
        finally:
1112
1324
            f.close()
 
1325
        self._set_mtime(name)
1113
1326
        self._set_mode(trans_id, mode_id, S_ISREG)
 
1327
        # It is unfortunate we have to use lstat instead of fstat, but we just
 
1328
        # used utime and chmod on the file, so we need the accurate final
 
1329
        # details.
 
1330
        if sha1 is not None:
 
1331
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1114
1332
 
1115
1333
    def _read_file_chunks(self, trans_id):
1116
1334
        cur_file = open(self._limbo_name(trans_id), 'rb')
1122
1340
    def _read_symlink_target(self, trans_id):
1123
1341
        return os.readlink(self._limbo_name(trans_id))
1124
1342
 
 
1343
    def _set_mtime(self, path):
 
1344
        """All files that are created get the same mtime.
 
1345
 
 
1346
        This time is set by the first object to be created.
 
1347
        """
 
1348
        if self._creation_mtime is None:
 
1349
            self._creation_mtime = time.time()
 
1350
        os.utime(path, (self._creation_mtime, self._creation_mtime))
 
1351
 
1125
1352
    def create_hardlink(self, path, trans_id):
1126
1353
        """Schedule creation of a hard link"""
1127
1354
        name = self._limbo_name(trans_id)
1166
1393
    def cancel_creation(self, trans_id):
1167
1394
        """Cancel the creation of new file contents."""
1168
1395
        del self._new_contents[trans_id]
 
1396
        if trans_id in self._observed_sha1s:
 
1397
            del self._observed_sha1s[trans_id]
1169
1398
        children = self._limbo_children.get(trans_id)
1170
1399
        # if this is a limbo directory with children, move them before removing
1171
1400
        # the directory
1175
1404
            del self._limbo_children_names[trans_id]
1176
1405
        delete_any(self._limbo_name(trans_id))
1177
1406
 
 
1407
    def new_orphan(self, trans_id, parent_id):
 
1408
        # FIXME: There is no tree config, so we use the branch one (it's weird
 
1409
        # to define it this way as orphaning can only occur in a working tree,
 
1410
        # but that's all we have (for now). It will find the option in
 
1411
        # locations.conf or bazaar.conf though) -- vila 20100916
 
1412
        conf = self._tree.branch.get_config()
 
1413
        conf_var_name = 'bzr.transform.orphan_policy'
 
1414
        orphan_policy = conf.get_user_option(conf_var_name)
 
1415
        default_policy = orphaning_registry.default_key
 
1416
        if orphan_policy is None:
 
1417
            orphan_policy = default_policy
 
1418
        if orphan_policy not in orphaning_registry:
 
1419
            trace.warning('%s (from %s) is not a known policy, defaulting '
 
1420
                'to %s' % (orphan_policy, conf_var_name, default_policy))
 
1421
            orphan_policy = default_policy
 
1422
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1423
        handle_orphan(self, trans_id, parent_id)
 
1424
 
 
1425
 
 
1426
class OrphaningError(errors.BzrError):
 
1427
 
 
1428
    # Only bugs could lead to such exception being seen by the user
 
1429
    internal_error = True
 
1430
    _fmt = "Error while orphaning %s in %s directory"
 
1431
 
 
1432
    def __init__(self, orphan, parent):
 
1433
        errors.BzrError.__init__(self)
 
1434
        self.orphan = orphan
 
1435
        self.parent = parent
 
1436
 
 
1437
 
 
1438
class OrphaningForbidden(OrphaningError):
 
1439
 
 
1440
    _fmt = "Policy: %s doesn't allow creating orphans."
 
1441
 
 
1442
    def __init__(self, policy):
 
1443
        errors.BzrError.__init__(self)
 
1444
        self.policy = policy
 
1445
 
 
1446
 
 
1447
def move_orphan(tt, orphan_id, parent_id):
 
1448
    """See TreeTransformBase.new_orphan.
 
1449
 
 
1450
    This creates a new orphan in the `bzr-orphans` dir at the root of the
 
1451
    `TreeTransform`.
 
1452
 
 
1453
    :param tt: The TreeTransform orphaning `trans_id`.
 
1454
 
 
1455
    :param orphan_id: The trans id that should be orphaned.
 
1456
 
 
1457
    :param parent_id: The orphan parent trans id.
 
1458
    """
 
1459
    # Add the orphan dir if it doesn't exist
 
1460
    orphan_dir_basename = 'bzr-orphans'
 
1461
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
 
1462
    if tt.final_kind(od_id) is None:
 
1463
        tt.create_directory(od_id)
 
1464
    parent_path = tt._tree_id_paths[parent_id]
 
1465
    # Find a name that doesn't exist yet in the orphan dir
 
1466
    actual_name = tt.final_name(orphan_id)
 
1467
    new_name = tt._available_backup_name(actual_name, od_id)
 
1468
    tt.adjust_path(new_name, od_id, orphan_id)
 
1469
    trace.warning('%s has been orphaned in %s'
 
1470
                  % (joinpath(parent_path, actual_name), orphan_dir_basename))
 
1471
 
 
1472
 
 
1473
def refuse_orphan(tt, orphan_id, parent_id):
 
1474
    """See TreeTransformBase.new_orphan.
 
1475
 
 
1476
    This refuses to create orphan, letting the caller handle the conflict.
 
1477
    """
 
1478
    raise OrphaningForbidden('never')
 
1479
 
 
1480
 
 
1481
orphaning_registry = registry.Registry()
 
1482
orphaning_registry.register(
 
1483
    'conflict', refuse_orphan,
 
1484
    'Leave orphans in place and create a conflict on the directory.')
 
1485
orphaning_registry.register(
 
1486
    'move', move_orphan,
 
1487
    'Move orphans into the bzr-orphans directory.')
 
1488
orphaning_registry._set_default_key('conflict')
 
1489
 
1178
1490
 
1179
1491
class TreeTransform(DiskTreeTransform):
1180
1492
    """Represent a tree transformation.
1241
1553
    FileMover does not delete files until it is sure that a rollback will not
1242
1554
    happen.
1243
1555
    """
1244
 
    def __init__(self, tree, pb=DummyProgress()):
 
1556
    def __init__(self, tree, pb=None):
1245
1557
        """Note: a tree_write lock is taken on the tree.
1246
1558
 
1247
1559
        Use TreeTransform.finalize() to release the lock (can be omitted if
1252
1564
        try:
1253
1565
            limbodir = urlutils.local_path_from_url(
1254
1566
                tree._transport.abspath('limbo'))
1255
 
            try:
1256
 
                os.mkdir(limbodir)
1257
 
            except OSError, e:
1258
 
                if e.errno == errno.EEXIST:
1259
 
                    raise ExistingLimbo(limbodir)
 
1567
            osutils.ensure_empty_directory_exists(
 
1568
                limbodir,
 
1569
                errors.ExistingLimbo)
1260
1570
            deletiondir = urlutils.local_path_from_url(
1261
1571
                tree._transport.abspath('pending-deletion'))
1262
 
            try:
1263
 
                os.mkdir(deletiondir)
1264
 
            except OSError, e:
1265
 
                if e.errno == errno.EEXIST:
1266
 
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1572
            osutils.ensure_empty_directory_exists(
 
1573
                deletiondir,
 
1574
                errors.ExistingPendingDeletion)
1267
1575
        except:
1268
1576
            tree.unlock()
1269
1577
            raise
1298
1606
    def tree_kind(self, trans_id):
1299
1607
        """Determine the file kind in the working tree.
1300
1608
 
1301
 
        Raises NoSuchFile if the file does not exist
 
1609
        :returns: The file kind or None if the file does not exist
1302
1610
        """
1303
1611
        path = self._tree_id_paths.get(trans_id)
1304
1612
        if path is None:
1305
 
            raise NoSuchFile(None)
 
1613
            return None
1306
1614
        try:
1307
1615
            return file_kind(self._tree.abspath(path))
1308
 
        except OSError, e:
1309
 
            if e.errno != errno.ENOENT:
1310
 
                raise
1311
 
            else:
1312
 
                raise NoSuchFile(path)
 
1616
        except errors.NoSuchFile:
 
1617
            return None
1313
1618
 
1314
1619
    def _set_mode(self, trans_id, mode_id, typefunc):
1315
1620
        """Set the mode of new file contents.
1335
1640
            else:
1336
1641
                raise
1337
1642
        if typefunc(mode):
1338
 
            os.chmod(self._limbo_name(trans_id), mode)
 
1643
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1339
1644
 
1340
1645
    def iter_tree_children(self, parent_id):
1341
1646
        """Iterate through the entry's tree children, if any"""
1357
1662
                continue
1358
1663
            yield self.trans_id_tree_path(childpath)
1359
1664
 
 
1665
    def _generate_limbo_path(self, trans_id):
 
1666
        """Generate a limbo path using the final path if possible.
 
1667
 
 
1668
        This optimizes the performance of applying the tree transform by
 
1669
        avoiding renames.  These renames can be avoided only when the parent
 
1670
        directory is already scheduled for creation.
 
1671
 
 
1672
        If the final path cannot be used, falls back to using the trans_id as
 
1673
        the relpath.
 
1674
        """
 
1675
        parent = self._new_parent.get(trans_id)
 
1676
        # if the parent directory is already in limbo (e.g. when building a
 
1677
        # tree), choose a limbo name inside the parent, to reduce further
 
1678
        # renames.
 
1679
        use_direct_path = False
 
1680
        if self._new_contents.get(parent) == 'directory':
 
1681
            filename = self._new_name.get(trans_id)
 
1682
            if filename is not None:
 
1683
                if parent not in self._limbo_children:
 
1684
                    self._limbo_children[parent] = set()
 
1685
                    self._limbo_children_names[parent] = {}
 
1686
                    use_direct_path = True
 
1687
                # the direct path can only be used if no other file has
 
1688
                # already taken this pathname, i.e. if the name is unused, or
 
1689
                # if it is already associated with this trans_id.
 
1690
                elif self._case_sensitive_target:
 
1691
                    if (self._limbo_children_names[parent].get(filename)
 
1692
                        in (trans_id, None)):
 
1693
                        use_direct_path = True
 
1694
                else:
 
1695
                    for l_filename, l_trans_id in\
 
1696
                        self._limbo_children_names[parent].iteritems():
 
1697
                        if l_trans_id == trans_id:
 
1698
                            continue
 
1699
                        if l_filename.lower() == filename.lower():
 
1700
                            break
 
1701
                    else:
 
1702
                        use_direct_path = True
 
1703
 
 
1704
        if not use_direct_path:
 
1705
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1706
 
 
1707
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1708
        self._limbo_children[parent].add(trans_id)
 
1709
        self._limbo_children_names[parent][filename] = trans_id
 
1710
        return limbo_name
 
1711
 
1360
1712
 
1361
1713
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1362
1714
        """Apply all changes to the inventory and filesystem.
1372
1724
            calculating one.
1373
1725
        :param _mover: Supply an alternate FileMover, for testing
1374
1726
        """
 
1727
        for hook in MutableTree.hooks['pre_transform']:
 
1728
            hook(self._tree, self)
1375
1729
        if not no_conflicts:
1376
 
            conflicts = self.find_conflicts()
1377
 
            if len(conflicts) != 0:
1378
 
                raise MalformedTransform(conflicts=conflicts)
1379
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1730
            self._check_malformed()
 
1731
        child_pb = ui.ui_factory.nested_progress_bar()
1380
1732
        try:
1381
1733
            if precomputed_delta is None:
1382
 
                child_pb.update('Apply phase', 0, 2)
 
1734
                child_pb.update(gettext('Apply phase'), 0, 2)
1383
1735
                inventory_delta = self._generate_inventory_delta()
1384
1736
                offset = 1
1385
1737
            else:
1390
1742
            else:
1391
1743
                mover = _mover
1392
1744
            try:
1393
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1745
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1394
1746
                self._apply_removals(mover)
1395
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1747
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1396
1748
                modified_paths = self._apply_insertions(mover)
1397
1749
            except:
1398
1750
                mover.rollback()
1401
1753
                mover.apply_deletions()
1402
1754
        finally:
1403
1755
            child_pb.finished()
 
1756
        if self.final_file_id(self.root) is None:
 
1757
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1404
1758
        self._tree.apply_inventory_delta(inventory_delta)
 
1759
        self._apply_observed_sha1s()
1405
1760
        self._done = True
1406
1761
        self.finalize()
1407
1762
        return _TransformResults(modified_paths, self.rename_count)
1409
1764
    def _generate_inventory_delta(self):
1410
1765
        """Generate an inventory delta for the current transform."""
1411
1766
        inventory_delta = []
1412
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1767
        child_pb = ui.ui_factory.nested_progress_bar()
1413
1768
        new_paths = self._inventory_altered()
1414
1769
        total_entries = len(new_paths) + len(self._removed_id)
1415
1770
        try:
1416
1771
            for num, trans_id in enumerate(self._removed_id):
1417
1772
                if (num % 10) == 0:
1418
 
                    child_pb.update('removing file', num, total_entries)
 
1773
                    child_pb.update(gettext('removing file'), num, total_entries)
1419
1774
                if trans_id == self._new_root:
1420
1775
                    file_id = self._tree.get_root_id()
1421
1776
                else:
1433
1788
            final_kinds = {}
1434
1789
            for num, (path, trans_id) in enumerate(new_paths):
1435
1790
                if (num % 10) == 0:
1436
 
                    child_pb.update('adding file',
 
1791
                    child_pb.update(gettext('adding file'),
1437
1792
                                    num + len(self._removed_id), total_entries)
1438
1793
                file_id = new_path_file_ids[trans_id]
1439
1794
                if file_id is None:
1440
1795
                    continue
1441
1796
                needs_entry = False
1442
 
                try:
1443
 
                    kind = self.final_kind(trans_id)
1444
 
                except NoSuchFile:
 
1797
                kind = self.final_kind(trans_id)
 
1798
                if kind is None:
1445
1799
                    kind = self._tree.stored_kind(file_id)
1446
1800
                parent_trans_id = self.final_parent(trans_id)
1447
1801
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1478
1832
        """
1479
1833
        tree_paths = list(self._tree_path_ids.iteritems())
1480
1834
        tree_paths.sort(reverse=True)
1481
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1835
        child_pb = ui.ui_factory.nested_progress_bar()
1482
1836
        try:
1483
 
            for num, data in enumerate(tree_paths):
1484
 
                path, trans_id = data
1485
 
                child_pb.update('removing file', num, len(tree_paths))
 
1837
            for num, (path, trans_id) in enumerate(tree_paths):
 
1838
                # do not attempt to move root into a subdirectory of itself.
 
1839
                if path == '':
 
1840
                    continue
 
1841
                child_pb.update(gettext('removing file'), num, len(tree_paths))
1486
1842
                full_path = self._tree.abspath(path)
1487
1843
                if trans_id in self._removed_contents:
1488
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
1489
 
                                     trans_id))
1490
 
                elif trans_id in self._new_name or trans_id in \
1491
 
                    self._new_parent:
 
1844
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
1845
                    mover.pre_delete(full_path, delete_path)
 
1846
                elif (trans_id in self._new_name
 
1847
                      or trans_id in self._new_parent):
1492
1848
                    try:
1493
1849
                        mover.rename(full_path, self._limbo_name(trans_id))
1494
 
                    except OSError, e:
 
1850
                    except errors.TransformRenameFailed, e:
1495
1851
                        if e.errno != errno.ENOENT:
1496
1852
                            raise
1497
1853
                    else:
1513
1869
        modified_paths = []
1514
1870
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1515
1871
                                 new_paths)
1516
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1872
        child_pb = ui.ui_factory.nested_progress_bar()
1517
1873
        try:
1518
1874
            for num, (path, trans_id) in enumerate(new_paths):
1519
1875
                if (num % 10) == 0:
1520
 
                    child_pb.update('adding file', num, len(new_paths))
 
1876
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1521
1877
                full_path = self._tree.abspath(path)
1522
1878
                if trans_id in self._needs_rename:
1523
1879
                    try:
1524
1880
                        mover.rename(self._limbo_name(trans_id), full_path)
1525
 
                    except OSError, e:
 
1881
                    except errors.TransformRenameFailed, e:
1526
1882
                        # We may be renaming a dangling inventory id
1527
1883
                        if e.errno != errno.ENOENT:
1528
1884
                            raise
1529
1885
                    else:
1530
1886
                        self.rename_count += 1
 
1887
                    # TODO: if trans_id in self._observed_sha1s, we should
 
1888
                    #       re-stat the final target, since ctime will be
 
1889
                    #       updated by the change.
1531
1890
                if (trans_id in self._new_contents or
1532
1891
                    self.path_changed(trans_id)):
1533
1892
                    if trans_id in self._new_contents:
1534
1893
                        modified_paths.append(full_path)
1535
1894
                if trans_id in self._new_executability:
1536
1895
                    self._set_executability(path, trans_id)
 
1896
                if trans_id in self._observed_sha1s:
 
1897
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
 
1898
                    st = osutils.lstat(full_path)
 
1899
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1537
1900
        finally:
1538
1901
            child_pb.finished()
 
1902
        for path, trans_id in new_paths:
 
1903
            # new_paths includes stuff like workingtree conflicts. Only the
 
1904
            # stuff in new_contents actually comes from limbo.
 
1905
            if trans_id in self._limbo_files:
 
1906
                del self._limbo_files[trans_id]
1539
1907
        self._new_contents.clear()
1540
1908
        return modified_paths
1541
1909
 
 
1910
    def _apply_observed_sha1s(self):
 
1911
        """After we have finished renaming everything, update observed sha1s
 
1912
 
 
1913
        This has to be done after self._tree.apply_inventory_delta, otherwise
 
1914
        it doesn't know anything about the files we are updating. Also, we want
 
1915
        to do this as late as possible, so that most entries end up cached.
 
1916
        """
 
1917
        # TODO: this doesn't update the stat information for directories. So
 
1918
        #       the first 'bzr status' will still need to rewrite
 
1919
        #       .bzr/checkout/dirstate. However, we at least don't need to
 
1920
        #       re-read all of the files.
 
1921
        # TODO: If the operation took a while, we could do a time.sleep(3) here
 
1922
        #       to allow the clock to tick over and ensure we won't have any
 
1923
        #       problems. (we could observe start time, and finish time, and if
 
1924
        #       it is less than eg 10% overhead, add a sleep call.)
 
1925
        paths = FinalPaths(self)
 
1926
        for trans_id, observed in self._observed_sha1s.iteritems():
 
1927
            path = paths.get_path(trans_id)
 
1928
            # We could get the file_id, but dirstate prefers to use the path
 
1929
            # anyway, and it is 'cheaper' to determine.
 
1930
            # file_id = self._new_id[trans_id]
 
1931
            self._tree._observed_sha1(None, path, observed)
 
1932
 
1542
1933
 
1543
1934
class TransformPreview(DiskTreeTransform):
1544
1935
    """A TreeTransform for generating preview trees.
1548
1939
    unversioned files in the input tree.
1549
1940
    """
1550
1941
 
1551
 
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
 
1942
    def __init__(self, tree, pb=None, case_sensitive=True):
1552
1943
        tree.lock_read()
1553
1944
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1554
1945
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1559
1950
    def tree_kind(self, trans_id):
1560
1951
        path = self._tree_id_paths.get(trans_id)
1561
1952
        if path is None:
1562
 
            raise NoSuchFile(None)
1563
 
        file_id = self._tree.path2id(path)
1564
 
        return self._tree.kind(file_id)
 
1953
            return None
 
1954
        kind = self._tree.path_content_summary(path)[0]
 
1955
        if kind == 'missing':
 
1956
            kind = None
 
1957
        return kind
1565
1958
 
1566
1959
    def _set_mode(self, trans_id, mode_id, typefunc):
1567
1960
        """Set the mode of new file contents.
1587
1980
            childpath = joinpath(path, child)
1588
1981
            yield self.trans_id_tree_path(childpath)
1589
1982
 
1590
 
 
1591
 
class _PreviewTree(tree.Tree):
 
1983
    def new_orphan(self, trans_id, parent_id):
 
1984
        raise NotImplementedError(self.new_orphan)
 
1985
 
 
1986
 
 
1987
class _PreviewTree(tree.InventoryTree):
1592
1988
    """Partial implementation of Tree to support show_diff_trees"""
1593
1989
 
1594
1990
    def __init__(self, transform):
1599
1995
        self._all_children_cache = {}
1600
1996
        self._path2trans_id_cache = {}
1601
1997
        self._final_name_cache = {}
1602
 
 
1603
 
    def _changes(self, file_id):
1604
 
        for changes in self._transform.iter_changes():
1605
 
            if changes[0] == file_id:
1606
 
                return changes
 
1998
        self._iter_changes_cache = dict((c[0], c) for c in
 
1999
                                        self._transform.iter_changes())
1607
2000
 
1608
2001
    def _content_change(self, file_id):
1609
2002
        """Return True if the content of this file changed"""
1610
 
        changes = self._changes(file_id)
 
2003
        changes = self._iter_changes_cache.get(file_id)
1611
2004
        # changes[2] is true if the file content changed.  See
1612
2005
        # InterTree.iter_changes.
1613
2006
        return (changes is not None and changes[2])
1626
2019
                yield self._get_repository().revision_tree(revision_id)
1627
2020
 
1628
2021
    def _get_file_revision(self, file_id, vf, tree_revision):
1629
 
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
2022
        parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
1630
2023
                       self._iter_parent_trees()]
1631
2024
        vf.add_lines((file_id, tree_revision), parent_keys,
1632
 
                     self.get_file(file_id).readlines())
 
2025
                     self.get_file_lines(file_id))
1633
2026
        repo = self._get_repository()
1634
2027
        base_vf = repo.texts
1635
2028
        if base_vf not in vf.fallback_versionedfiles:
1636
2029
            vf.fallback_versionedfiles.append(base_vf)
1637
2030
        return tree_revision
1638
2031
 
1639
 
    def _stat_limbo_file(self, file_id):
1640
 
        trans_id = self._transform.trans_id_file_id(file_id)
 
2032
    def _stat_limbo_file(self, file_id=None, trans_id=None):
 
2033
        if trans_id is None:
 
2034
            trans_id = self._transform.trans_id_file_id(file_id)
1641
2035
        name = self._transform._limbo_name(trans_id)
1642
2036
        return os.lstat(name)
1643
2037
 
1657
2051
            executable = self.is_executable(file_id, path)
1658
2052
        return kind, executable, None
1659
2053
 
 
2054
    def is_locked(self):
 
2055
        return False
 
2056
 
1660
2057
    def lock_read(self):
1661
2058
        # Perhaps in theory, this should lock the TreeTransform?
1662
 
        pass
 
2059
        return self
1663
2060
 
1664
2061
    def unlock(self):
1665
2062
        pass
1682
2079
    def __iter__(self):
1683
2080
        return iter(self.all_file_ids())
1684
2081
 
1685
 
    def has_id(self, file_id):
 
2082
    def _has_id(self, file_id, fallback_check):
1686
2083
        if file_id in self._transform._r_new_id:
1687
2084
            return True
1688
2085
        elif file_id in set([self._transform.tree_file_id(trans_id) for
1689
2086
            trans_id in self._transform._removed_id]):
1690
2087
            return False
1691
2088
        else:
1692
 
            return self._transform._tree.has_id(file_id)
 
2089
            return fallback_check(file_id)
 
2090
 
 
2091
    def has_id(self, file_id):
 
2092
        return self._has_id(file_id, self._transform._tree.has_id)
 
2093
 
 
2094
    def has_or_had_id(self, file_id):
 
2095
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
1693
2096
 
1694
2097
    def _path2trans_id(self, path):
1695
2098
        # We must not use None here, because that is a valid value to store.
1748
2151
            if self._transform.final_file_id(trans_id) is None:
1749
2152
                yield self._final_paths._determine_path(trans_id)
1750
2153
 
1751
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
 
2154
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
2155
        yield_parents=False):
1752
2156
        for trans_id, parent_file_id in ordered_entries:
1753
2157
            file_id = self._transform.final_file_id(trans_id)
1754
2158
            if file_id is None:
1756
2160
            if (specific_file_ids is not None
1757
2161
                and file_id not in specific_file_ids):
1758
2162
                continue
1759
 
            try:
1760
 
                kind = self._transform.final_kind(trans_id)
1761
 
            except NoSuchFile:
 
2163
            kind = self._transform.final_kind(trans_id)
 
2164
            if kind is None:
1762
2165
                kind = self._transform._tree.stored_kind(file_id)
1763
2166
            new_entry = inventory.make_entry(
1764
2167
                kind,
1780
2183
                ordered_ids.append((trans_id, parent_file_id))
1781
2184
        return ordered_ids
1782
2185
 
1783
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
2186
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1784
2187
        # This may not be a maximally efficient implementation, but it is
1785
2188
        # reasonably straightforward.  An implementation that grafts the
1786
2189
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1788
2191
        # position.
1789
2192
        ordered_ids = self._list_files_by_dir()
1790
2193
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1791
 
                                                      specific_file_ids):
 
2194
            specific_file_ids, yield_parents=yield_parents):
1792
2195
            yield unicode(self._final_paths.get_path(trans_id)), entry
1793
2196
 
1794
2197
    def _iter_entries_for_dir(self, dir_path):
1841
2244
    def get_file_mtime(self, file_id, path=None):
1842
2245
        """See Tree.get_file_mtime"""
1843
2246
        if not self._content_change(file_id):
1844
 
            return self._transform._tree.get_file_mtime(file_id, path)
 
2247
            return self._transform._tree.get_file_mtime(file_id)
1845
2248
        return self._stat_limbo_file(file_id).st_mtime
1846
2249
 
1847
2250
    def _file_size(self, entry, stat_value):
1849
2252
 
1850
2253
    def get_file_size(self, file_id):
1851
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
1852
2261
        if self.kind(file_id) == 'file':
1853
2262
            return self._transform._tree.get_file_size(file_id)
1854
2263
        else:
1855
2264
            return None
1856
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
 
1857
2278
    def get_file_sha1(self, file_id, path=None, stat_value=None):
1858
2279
        trans_id = self._transform.trans_id_file_id(file_id)
1859
2280
        kind = self._transform._new_contents.get(trans_id)
1882
2303
            except errors.NoSuchId:
1883
2304
                return False
1884
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
 
1885
2315
    def path_content_summary(self, path):
1886
2316
        trans_id = self._path2trans_id(path)
1887
2317
        tt = self._transform
1900
2330
            if kind == 'file':
1901
2331
                statval = os.lstat(limbo_name)
1902
2332
                size = statval.st_size
1903
 
                if not supports_executable():
1904
 
                    executable = None
 
2333
                if not tt._limbo_supports_executable():
 
2334
                    executable = False
1905
2335
                else:
1906
2336
                    executable = statval.st_mode & S_IEXEC
1907
2337
            else:
1909
2339
                executable = None
1910
2340
            if kind == 'symlink':
1911
2341
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1912
 
        if supports_executable():
1913
 
            executable = tt._new_executability.get(trans_id, executable)
 
2342
        executable = tt._new_executability.get(trans_id, executable)
1914
2343
        return kind, size, executable, link_or_sha1
1915
2344
 
1916
2345
    def iter_changes(self, from_tree, include_unchanged=False,
1947
2376
 
1948
2377
    def annotate_iter(self, file_id,
1949
2378
                      default_revision=_mod_revision.CURRENT_REVISION):
1950
 
        changes = self._changes(file_id)
 
2379
        changes = self._iter_changes_cache.get(file_id)
1951
2380
        if changes is None:
1952
2381
            get_old = True
1953
2382
        else:
1965
2394
            return old_annotation
1966
2395
        if not changed_content:
1967
2396
            return old_annotation
 
2397
        # TODO: This is doing something similar to what WT.annotate_iter is
 
2398
        #       doing, however it fails slightly because it doesn't know what
 
2399
        #       the *other* revision_id is, so it doesn't know how to give the
 
2400
        #       other as the origin for some lines, they all get
 
2401
        #       'default_revision'
 
2402
        #       It would be nice to be able to use the new Annotator based
 
2403
        #       approach, as well.
1968
2404
        return annotate.reannotate([old_annotation],
1969
2405
                                   self.get_file(file_id).readlines(),
1970
2406
                                   default_revision)
1971
2407
 
1972
 
    def get_symlink_target(self, file_id):
 
2408
    def get_symlink_target(self, file_id, path=None):
1973
2409
        """See Tree.get_symlink_target"""
1974
2410
        if not self._content_change(file_id):
1975
2411
            return self._transform._tree.get_symlink_target(file_id)
1990
2426
                path_from_root = self._final_paths.get_path(child_id)
1991
2427
                basename = self._transform.final_name(child_id)
1992
2428
                file_id = self._transform.final_file_id(child_id)
1993
 
                try:
1994
 
                    kind = self._transform.final_kind(child_id)
 
2429
                kind  = self._transform.final_kind(child_id)
 
2430
                if kind is not None:
1995
2431
                    versioned_kind = kind
1996
 
                except NoSuchFile:
 
2432
                else:
1997
2433
                    kind = 'unknown'
1998
2434
                    versioned_kind = self._transform._tree.stored_kind(file_id)
1999
2435
                if versioned_kind == 'directory':
2036
2472
        self.transform = transform
2037
2473
 
2038
2474
    def _determine_path(self, trans_id):
2039
 
        if trans_id == self.transform.root:
 
2475
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2040
2476
            return ""
2041
2477
        name = self.transform.final_name(trans_id)
2042
2478
        parent_id = self.transform.final_parent(trans_id)
2112
2548
    for num, _unused in enumerate(wt.all_file_ids()):
2113
2549
        if num > 0:  # more than just a root
2114
2550
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2115
 
    existing_files = set()
2116
 
    for dir, files in wt.walkdirs():
2117
 
        existing_files.update(f[0] for f in files)
2118
2551
    file_trans_id = {}
2119
 
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2552
    top_pb = ui.ui_factory.nested_progress_bar()
2120
2553
    pp = ProgressPhase("Build phase", 2, top_pb)
2121
 
    if tree.inventory.root is not None:
 
2554
    if tree.get_root_id() is not None:
2122
2555
        # This is kind of a hack: we should be altering the root
2123
2556
        # as part of the regular tree shape diff logic.
2124
2557
        # The conditional test here is to avoid doing an
2135
2568
        pp.next_phase()
2136
2569
        file_trans_id[wt.get_root_id()] = \
2137
2570
            tt.trans_id_tree_file_id(wt.get_root_id())
2138
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2571
        pb = ui.ui_factory.nested_progress_bar()
2139
2572
        try:
2140
2573
            deferred_contents = []
2141
2574
            num = 0
2142
 
            total = len(tree.inventory)
 
2575
            total = len(tree.all_file_ids())
2143
2576
            if delta_from_tree:
2144
2577
                precomputed_delta = []
2145
2578
            else:
2146
2579
                precomputed_delta = None
 
2580
            # Check if tree inventory has content. If so, we populate
 
2581
            # existing_files with the directory content. If there are no
 
2582
            # entries we skip populating existing_files as its not used.
 
2583
            # This improves performance and unncessary work on large
 
2584
            # directory trees. (#501307)
 
2585
            if total > 0:
 
2586
                existing_files = set()
 
2587
                for dir, files in wt.walkdirs():
 
2588
                    existing_files.update(f[0] for f in files)
2147
2589
            for num, (tree_path, entry) in \
2148
 
                enumerate(tree.inventory.iter_entries_by_dir()):
2149
 
                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)
2150
2592
                if entry.parent_id is None:
2151
2593
                    continue
2152
2594
                reparent = False
2158
2600
                    kind = file_kind(target_path)
2159
2601
                    if kind == "directory":
2160
2602
                        try:
2161
 
                            bzrdir.BzrDir.open(target_path)
 
2603
                            controldir.ControlDir.open(target_path)
2162
2604
                        except errors.NotBranchError:
2163
2605
                            pass
2164
2606
                        else:
2179
2621
                    executable = tree.is_executable(file_id, tree_path)
2180
2622
                    if executable:
2181
2623
                        tt.set_executability(executable, trans_id)
2182
 
                    trans_data = (trans_id, tree_path)
 
2624
                    trans_data = (trans_id, tree_path, entry.text_sha1)
2183
2625
                    deferred_contents.append((file_id, trans_data))
2184
2626
                else:
2185
2627
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2201
2643
            precomputed_delta = None
2202
2644
        conflicts = cook_conflicts(raw_conflicts, tt)
2203
2645
        for conflict in conflicts:
2204
 
            warning(conflict)
 
2646
            trace.warning(unicode(conflict))
2205
2647
        try:
2206
2648
            wt.add_conflicts(conflicts)
2207
2649
        except errors.UnsupportedOperation:
2222
2664
        new_desired_files = desired_files
2223
2665
    else:
2224
2666
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2225
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2226
 
                         in iter if not (c or e[0] != e[1]))
 
2667
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2668
                     in iter if not (c or e[0] != e[1])]
 
2669
        if accelerator_tree.supports_content_filtering():
 
2670
            unchanged = [(f, p) for (f, p) in unchanged
 
2671
                         if not accelerator_tree.iter_search_rules([p]).next()]
 
2672
        unchanged = dict(unchanged)
2227
2673
        new_desired_files = []
2228
2674
        count = 0
2229
 
        for file_id, (trans_id, tree_path) in desired_files:
 
2675
        for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2230
2676
            accelerator_path = unchanged.get(file_id)
2231
2677
            if accelerator_path is None:
2232
 
                new_desired_files.append((file_id, (trans_id, tree_path)))
 
2678
                new_desired_files.append((file_id,
 
2679
                    (trans_id, tree_path, text_sha1)))
2233
2680
                continue
2234
 
            pb.update('Adding file contents', count + offset, total)
 
2681
            pb.update(gettext('Adding file contents'), count + offset, total)
2235
2682
            if hardlink:
2236
2683
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2237
2684
                                   trans_id)
2242
2689
                    contents = filtered_output_bytes(contents, filters,
2243
2690
                        ContentFilterContext(tree_path, tree))
2244
2691
                try:
2245
 
                    tt.create_file(contents, trans_id)
 
2692
                    tt.create_file(contents, trans_id, sha1=text_sha1)
2246
2693
                finally:
2247
2694
                    try:
2248
2695
                        contents.close()
2251
2698
                        pass
2252
2699
            count += 1
2253
2700
        offset += count
2254
 
    for count, ((trans_id, tree_path), contents) in enumerate(
 
2701
    for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2255
2702
            tree.iter_files_bytes(new_desired_files)):
2256
2703
        if wt.supports_content_filtering():
2257
2704
            filters = wt._content_filter_stack(tree_path)
2258
2705
            contents = filtered_output_bytes(contents, filters,
2259
2706
                ContentFilterContext(tree_path, tree))
2260
 
        tt.create_file(contents, trans_id)
2261
 
        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)
2262
2709
 
2263
2710
 
2264
2711
def _reparent_children(tt, old_parent, new_parent):
2265
2712
    for child in tt.iter_tree_children(old_parent):
2266
2713
        tt.adjust_path(tt.final_name(child), new_parent, child)
2267
2714
 
 
2715
 
2268
2716
def _reparent_transform_children(tt, old_parent, new_parent):
2269
2717
    by_parent = tt.by_parent()
2270
2718
    for child in by_parent[old_parent]:
2271
2719
        tt.adjust_path(tt.final_name(child), new_parent, child)
2272
2720
    return by_parent[old_parent]
2273
2721
 
 
2722
 
2274
2723
def _content_match(tree, entry, file_id, kind, target_path):
2275
2724
    if entry.kind != kind:
2276
2725
        return False
2277
2726
    if entry.kind == "directory":
2278
2727
        return True
2279
2728
    if entry.kind == "file":
2280
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2281
 
            return True
 
2729
        f = file(target_path, 'rb')
 
2730
        try:
 
2731
            if tree.get_file_text(file_id) == f.read():
 
2732
                return True
 
2733
        finally:
 
2734
            f.close()
2282
2735
    elif entry.kind == "symlink":
2283
2736
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
2284
2737
            return True
2336
2789
        raise errors.BadFileKindError(name, kind)
2337
2790
 
2338
2791
 
2339
 
@deprecated_function(deprecated_in((1, 9, 0)))
2340
 
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2341
 
    """Create new file contents according to an inventory entry.
2342
 
 
2343
 
    DEPRECATED.  Use create_from_tree instead.
 
2792
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2793
    filter_tree_path=None):
 
2794
    """Create new file contents according to tree contents.
 
2795
    
 
2796
    :param filter_tree_path: the tree path to use to lookup
 
2797
      content filters to apply to the bytes output in the working tree.
 
2798
      This only applies if the working tree supports content filtering.
2344
2799
    """
2345
 
    if entry.kind == "file":
2346
 
        if lines is None:
2347
 
            lines = tree.get_file(entry.file_id).readlines()
2348
 
        tt.create_file(lines, trans_id, mode_id=mode_id)
2349
 
    elif entry.kind == "symlink":
2350
 
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2351
 
    elif entry.kind == "directory":
2352
 
        tt.create_directory(trans_id)
2353
 
 
2354
 
 
2355
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2356
 
    """Create new file contents according to tree contents."""
2357
2800
    kind = tree.kind(file_id)
2358
2801
    if kind == 'directory':
2359
2802
        tt.create_directory(trans_id)
2364
2807
                bytes = tree_file.readlines()
2365
2808
            finally:
2366
2809
                tree_file.close()
 
2810
        wt = tt._tree
 
2811
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2812
            filters = wt._content_filter_stack(filter_tree_path)
 
2813
            bytes = filtered_output_bytes(bytes, filters,
 
2814
                ContentFilterContext(filter_tree_path, tree))
2367
2815
        tt.create_file(bytes, trans_id)
2368
2816
    elif kind == "symlink":
2369
2817
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2377
2825
        tt.set_executability(entry.executable, trans_id)
2378
2826
 
2379
2827
 
 
2828
@deprecated_function(deprecated_in((2, 3, 0)))
2380
2829
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2381
2830
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2382
2831
 
2383
2832
 
 
2833
@deprecated_function(deprecated_in((2, 3, 0)))
2384
2834
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2385
2835
    """Produce a backup-style name that appears to be available"""
2386
2836
    def name_gen():
2393
2843
            return new_name
2394
2844
 
2395
2845
 
2396
 
def _entry_changes(file_id, entry, working_tree):
2397
 
    """Determine in which ways the inventory entry has changed.
2398
 
 
2399
 
    Returns booleans: has_contents, content_mod, meta_mod
2400
 
    has_contents means there are currently contents, but they differ
2401
 
    contents_mod means contents need to be modified
2402
 
    meta_mod means the metadata needs to be modified
2403
 
    """
2404
 
    cur_entry = working_tree.inventory[file_id]
2405
 
    try:
2406
 
        working_kind = working_tree.kind(file_id)
2407
 
        has_contents = True
2408
 
    except NoSuchFile:
2409
 
        has_contents = False
2410
 
        contents_mod = True
2411
 
        meta_mod = False
2412
 
    if has_contents is True:
2413
 
        if entry.kind != working_kind:
2414
 
            contents_mod, meta_mod = True, False
2415
 
        else:
2416
 
            cur_entry._read_tree_state(working_tree.id2path(file_id),
2417
 
                                       working_tree)
2418
 
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
2419
 
            cur_entry._forget_tree_state()
2420
 
    return has_contents, contents_mod, meta_mod
2421
 
 
2422
 
 
2423
2846
def revert(working_tree, target_tree, filenames, backups=False,
2424
 
           pb=DummyProgress(), change_reporter=None):
 
2847
           pb=None, change_reporter=None):
2425
2848
    """Revert a working tree's contents to those of a target tree."""
2426
2849
    target_tree.lock_read()
 
2850
    pb = ui.ui_factory.nested_progress_bar()
2427
2851
    tt = TreeTransform(working_tree, pb)
2428
2852
    try:
2429
2853
        pp = ProgressPhase("Revert phase", 3, pb)
2434
2858
                unversioned_filter=working_tree.is_ignored)
2435
2859
            delta.report_changes(tt.iter_changes(), change_reporter)
2436
2860
        for conflict in conflicts:
2437
 
            warning(conflict)
 
2861
            trace.warning(unicode(conflict))
2438
2862
        pp.next_phase()
2439
2863
        tt.apply()
2440
2864
        working_tree.set_merge_modified(merge_modified)
2448
2872
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2449
2873
                              backups, pp, basis_tree=None,
2450
2874
                              merge_modified=None):
2451
 
    pp.next_phase()
2452
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2875
    child_pb = ui.ui_factory.nested_progress_bar()
2453
2876
    try:
2454
2877
        if merge_modified is None:
2455
2878
            merge_modified = working_tree.merge_modified()
2458
2881
                                      merge_modified, basis_tree)
2459
2882
    finally:
2460
2883
        child_pb.finished()
2461
 
    pp.next_phase()
2462
 
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2884
    child_pb = ui.ui_factory.nested_progress_bar()
2463
2885
    try:
2464
2886
        raw_conflicts = resolve_conflicts(tt, child_pb,
2465
2887
            lambda t, c: conflict_pass(t, c, target_tree))
2473
2895
                 backups, merge_modified, basis_tree=None):
2474
2896
    if basis_tree is not None:
2475
2897
        basis_tree.lock_read()
2476
 
    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,
2477
2903
        specific_files=specific_files, pb=pb)
2478
2904
    if target_tree.get_root_id() is None:
2479
2905
        skip_root = True
2483
2909
        deferred_files = []
2484
2910
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2485
2911
                kind, executable) in enumerate(change_list):
2486
 
            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:
2487
2919
                continue
2488
2920
            trans_id = tt.trans_id_file_id(file_id)
2489
2921
            mode_id = None
2490
2922
            if changed_content:
2491
2923
                keep_content = False
2492
 
                if kind[0] == 'file' and (backups or kind[1] is None):
 
2924
                if wt_kind == 'file' and (backups or target_kind is None):
2493
2925
                    wt_sha1 = working_tree.get_file_sha1(file_id)
2494
2926
                    if merge_modified.get(file_id) != wt_sha1:
2495
2927
                        # acquire the basis tree lazily to prevent the
2498
2930
                        if basis_tree is None:
2499
2931
                            basis_tree = working_tree.basis_tree()
2500
2932
                            basis_tree.lock_read()
2501
 
                        if file_id in basis_tree:
 
2933
                        if basis_tree.has_id(file_id):
2502
2934
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
2503
2935
                                keep_content = True
2504
 
                        elif kind[1] is None and not versioned[1]:
 
2936
                        elif target_kind is None and not target_versioned:
2505
2937
                            keep_content = True
2506
 
                if kind[0] is not None:
 
2938
                if wt_kind is not None:
2507
2939
                    if not keep_content:
2508
2940
                        tt.delete_contents(trans_id)
2509
 
                    elif kind[1] is not None:
2510
 
                        parent_trans_id = tt.trans_id_file_id(parent[0])
2511
 
                        by_parent = tt.by_parent()
2512
 
                        backup_name = _get_backup_name(name[0], by_parent,
2513
 
                                                       parent_trans_id, tt)
 
2941
                    elif target_kind is not None:
 
2942
                        parent_trans_id = tt.trans_id_file_id(wt_parent)
 
2943
                        backup_name = tt._available_backup_name(
 
2944
                            wt_name, parent_trans_id)
2514
2945
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
2515
 
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
2516
 
                        if versioned == (True, True):
 
2946
                        new_trans_id = tt.create_path(wt_name, parent_trans_id)
 
2947
                        if wt_versioned and target_versioned:
2517
2948
                            tt.unversion_file(trans_id)
2518
2949
                            tt.version_file(file_id, new_trans_id)
2519
2950
                        # New contents should have the same unix perms as old
2520
2951
                        # contents
2521
2952
                        mode_id = trans_id
2522
2953
                        trans_id = new_trans_id
2523
 
                if kind[1] in ('directory', 'tree-reference'):
 
2954
                if target_kind in ('directory', 'tree-reference'):
2524
2955
                    tt.create_directory(trans_id)
2525
 
                    if kind[1] == 'tree-reference':
 
2956
                    if target_kind == 'tree-reference':
2526
2957
                        revision = target_tree.get_reference_revision(file_id,
2527
 
                                                                      path[1])
 
2958
                                                                      target_path)
2528
2959
                        tt.set_tree_reference(revision, trans_id)
2529
 
                elif kind[1] == 'symlink':
 
2960
                elif target_kind == 'symlink':
2530
2961
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
2531
2962
                                      trans_id)
2532
 
                elif kind[1] == 'file':
 
2963
                elif target_kind == 'file':
2533
2964
                    deferred_files.append((file_id, (trans_id, mode_id)))
2534
2965
                    if basis_tree is None:
2535
2966
                        basis_tree = working_tree.basis_tree()
2536
2967
                        basis_tree.lock_read()
2537
2968
                    new_sha1 = target_tree.get_file_sha1(file_id)
2538
 
                    if (file_id in basis_tree and new_sha1 ==
2539
 
                        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)):
2540
2971
                        if file_id in merge_modified:
2541
2972
                            del merge_modified[file_id]
2542
2973
                    else:
2543
2974
                        merge_modified[file_id] = new_sha1
2544
2975
 
2545
2976
                    # preserve the execute bit when backing up
2546
 
                    if keep_content and executable[0] == executable[1]:
2547
 
                        tt.set_executability(executable[1], trans_id)
2548
 
                elif kind[1] is not None:
2549
 
                    raise AssertionError(kind[1])
2550
 
            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:
2551
2982
                tt.version_file(file_id, trans_id)
2552
 
            if versioned == (True, False):
 
2983
            if wt_versioned and not target_versioned:
2553
2984
                tt.unversion_file(trans_id)
2554
 
            if (name[1] is not None and
2555
 
                (name[0] != name[1] or parent[0] != parent[1])):
2556
 
                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:
2557
2988
                    parent_trans = ROOT_PARENT
2558
2989
                else:
2559
 
                    parent_trans = tt.trans_id_file_id(parent[1])
2560
 
                tt.adjust_path(name[1], parent_trans, trans_id)
2561
 
            if executable[0] != executable[1] and kind[1] == "file":
2562
 
                tt.set_executability(executable[1], trans_id)
2563
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2564
 
            deferred_files):
2565
 
            tt.create_file(bytes, trans_id, mode_id)
 
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)
 
2993
                else:
 
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)
 
2997
        if working_tree.supports_content_filtering():
 
2998
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2999
                target_tree.iter_files_bytes(deferred_files)):
 
3000
                file_id = deferred_files[index][0]
 
3001
                # We're reverting a tree to the target tree so using the
 
3002
                # target tree to find the file path seems the best choice
 
3003
                # here IMO - Ian C 27/Oct/2009
 
3004
                filter_tree_path = target_tree.id2path(file_id)
 
3005
                filters = working_tree._content_filter_stack(filter_tree_path)
 
3006
                bytes = filtered_output_bytes(bytes, filters,
 
3007
                    ContentFilterContext(filter_tree_path, working_tree))
 
3008
                tt.create_file(bytes, trans_id, mode_id)
 
3009
        else:
 
3010
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
3011
                deferred_files):
 
3012
                tt.create_file(bytes, trans_id, mode_id)
 
3013
        tt.fixup_new_roots()
2566
3014
    finally:
2567
3015
        if basis_tree is not None:
2568
3016
            basis_tree.unlock()
2569
3017
    return merge_modified
2570
3018
 
2571
3019
 
2572
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
3020
def resolve_conflicts(tt, pb=None, pass_func=None):
2573
3021
    """Make many conflict-resolution attempts, but die if they fail"""
2574
3022
    if pass_func is None:
2575
3023
        pass_func = conflict_pass
2576
3024
    new_conflicts = set()
 
3025
    pb = ui.ui_factory.nested_progress_bar()
2577
3026
    try:
2578
3027
        for n in range(10):
2579
 
            pb.update('Resolution pass', n+1, 10)
 
3028
            pb.update(gettext('Resolution pass'), n+1, 10)
2580
3029
            conflicts = tt.find_conflicts()
2581
3030
            if len(conflicts) == 0:
2582
3031
                return new_conflicts
2583
3032
            new_conflicts.update(pass_func(tt, conflicts))
2584
3033
        raise MalformedTransform(conflicts=conflicts)
2585
3034
    finally:
2586
 
        pb.clear()
 
3035
        pb.finished()
2587
3036
 
2588
3037
 
2589
3038
def conflict_pass(tt, conflicts, path_tree=None):
2606
3055
                existing_file, new_file = conflict[2], conflict[1]
2607
3056
            else:
2608
3057
                existing_file, new_file = conflict[1], conflict[2]
2609
 
            new_name = tt.final_name(existing_file)+'.moved'
 
3058
            new_name = tt.final_name(existing_file) + '.moved'
2610
3059
            tt.adjust_path(new_name, final_parent, existing_file)
2611
3060
            new_conflicts.add((c_type, 'Moved existing file to',
2612
3061
                               existing_file, new_file))
2621
3070
 
2622
3071
        elif c_type == 'missing parent':
2623
3072
            trans_id = conflict[1]
2624
 
            try:
2625
 
                tt.cancel_deletion(trans_id)
2626
 
                new_conflicts.add(('deleting parent', 'Not deleting',
2627
 
                                   trans_id))
2628
 
            except KeyError:
 
3073
            if trans_id in tt._removed_contents:
 
3074
                cancel_deletion = True
 
3075
                orphans = tt._get_potential_orphans(trans_id)
 
3076
                if orphans:
 
3077
                    cancel_deletion = False
 
3078
                    # All children are orphans
 
3079
                    for o in orphans:
 
3080
                        try:
 
3081
                            tt.new_orphan(o, trans_id)
 
3082
                        except OrphaningError:
 
3083
                            # Something bad happened so we cancel the directory
 
3084
                            # deletion which will leave it in place with a
 
3085
                            # conflict. The user can deal with it from there.
 
3086
                            # Note that this also catch the case where we don't
 
3087
                            # want to create orphans and leave the directory in
 
3088
                            # place.
 
3089
                            cancel_deletion = True
 
3090
                            break
 
3091
                if cancel_deletion:
 
3092
                    # Cancel the directory deletion
 
3093
                    tt.cancel_deletion(trans_id)
 
3094
                    new_conflicts.add(('deleting parent', 'Not deleting',
 
3095
                                       trans_id))
 
3096
            else:
2629
3097
                create = True
2630
3098
                try:
2631
3099
                    tt.final_name(trans_id)
2634
3102
                        file_id = tt.final_file_id(trans_id)
2635
3103
                        if file_id is None:
2636
3104
                            file_id = tt.inactive_file_id(trans_id)
2637
 
                        entry = path_tree.inventory[file_id]
 
3105
                        _, entry = path_tree.iter_entries_by_dir(
 
3106
                            [file_id]).next()
2638
3107
                        # special-case the other tree root (move its
2639
3108
                        # children to current root)
2640
3109
                        if entry.parent_id is None:
2641
 
                            create=False
 
3110
                            create = False
2642
3111
                            moved = _reparent_transform_children(
2643
3112
                                tt, trans_id, tt.root)
2644
3113
                            for child in moved:
2655
3124
        elif c_type == 'unversioned parent':
2656
3125
            file_id = tt.inactive_file_id(conflict[1])
2657
3126
            # special-case the other tree root (move its children instead)
2658
 
            if path_tree and file_id in path_tree:
2659
 
                if path_tree.inventory[file_id].parent_id is None:
 
3127
            if path_tree and path_tree.has_id(file_id):
 
3128
                if path_tree.path2id('') == file_id:
 
3129
                    # This is the root entry, skip it
2660
3130
                    continue
2661
3131
            tt.version_file(file_id, conflict[1])
2662
3132
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2678
3148
 
2679
3149
def cook_conflicts(raw_conflicts, tt):
2680
3150
    """Generate a list of cooked conflicts, sorted by file path"""
2681
 
    from bzrlib.conflicts import Conflict
2682
3151
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
2683
 
    return sorted(conflict_iter, key=Conflict.sort_key)
 
3152
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
2684
3153
 
2685
3154
 
2686
3155
def iter_cook_conflicts(raw_conflicts, tt):
2687
 
    from bzrlib.conflicts import Conflict
2688
3156
    fp = FinalPaths(tt)
2689
3157
    for conflict in raw_conflicts:
2690
3158
        c_type = conflict[0]
2692
3160
        modified_path = fp.get_path(conflict[2])
2693
3161
        modified_id = tt.final_file_id(conflict[2])
2694
3162
        if len(conflict) == 3:
2695
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
2696
 
                                     file_id=modified_id)
 
3163
            yield conflicts.Conflict.factory(
 
3164
                c_type, action=action, path=modified_path, file_id=modified_id)
2697
3165
 
2698
3166
        else:
2699
3167
            conflicting_path = fp.get_path(conflict[3])
2700
3168
            conflicting_id = tt.final_file_id(conflict[3])
2701
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
2702
 
                                   file_id=modified_id,
2703
 
                                   conflict_path=conflicting_path,
2704
 
                                   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)
2705
3174
 
2706
3175
 
2707
3176
class _FileMover(object):
2712
3181
        self.pending_deletions = []
2713
3182
 
2714
3183
    def rename(self, from_, to):
2715
 
        """Rename a file from one path to another.  Functions like os.rename"""
 
3184
        """Rename a file from one path to another."""
2716
3185
        try:
2717
3186
            os.rename(from_, to)
2718
3187
        except OSError, e:
2719
3188
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2720
3189
                raise errors.FileExists(to, str(e))
2721
 
            raise
 
3190
            # normal OSError doesn't include filenames so it's hard to see where
 
3191
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
 
3192
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2722
3193
        self.past_renames.append((from_, to))
2723
3194
 
2724
3195
    def pre_delete(self, from_, to):
2734
3205
    def rollback(self):
2735
3206
        """Reverse all renames that have been performed"""
2736
3207
        for from_, to in reversed(self.past_renames):
2737
 
            os.rename(to, from_)
 
3208
            try:
 
3209
                os.rename(to, from_)
 
3210
            except OSError, e:
 
3211
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
2738
3212
        # after rollback, don't reuse _FileMover
2739
3213
        past_renames = None
2740
3214
        pending_deletions = None