/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to brzlib/transform.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
import contextlib
 
17
from __future__ import absolute_import
 
18
 
18
19
import os
19
20
import errno
20
21
from stat import S_ISREG, S_IEXEC
21
22
import time
22
23
 
23
 
from . import (
 
24
from brzlib import (
24
25
    config as _mod_config,
25
26
    errors,
26
27
    lazy_import,
29
30
    tree,
30
31
    )
31
32
lazy_import.lazy_import(globals(), """
32
 
from breezy import (
 
33
from brzlib import (
33
34
    annotate,
34
35
    bencode,
35
36
    controldir,
36
37
    commit,
37
38
    conflicts,
38
39
    delta,
39
 
    lock,
 
40
    inventory,
40
41
    multiparent,
41
42
    osutils,
42
43
    revision as _mod_revision,
43
44
    ui,
44
45
    urlutils,
45
46
    )
46
 
from breezy.bzr import (
47
 
    inventory,
48
 
    inventorytree,
49
 
    )
50
 
from breezy.i18n import gettext
 
47
from brzlib.i18n import gettext
51
48
""")
52
 
from .errors import (DuplicateKey, MalformedTransform,
53
 
                     ReusingTransform, CantMoveRoot,
54
 
                     ImmortalLimbo, NoFinalPath)
55
 
from .filters import filtered_output_bytes, ContentFilterContext
56
 
from .mutabletree import MutableTree
57
 
from .osutils import (
 
49
from brzlib.errors import (DuplicateKey, MalformedTransform,
 
50
                           ReusingTransform, CantMoveRoot,
 
51
                           ImmortalLimbo, NoFinalPath,
 
52
                           UnableCreateSymlink)
 
53
from brzlib.filters import filtered_output_bytes, ContentFilterContext
 
54
from brzlib.mutabletree import MutableTree
 
55
from brzlib.osutils import (
58
56
    delete_any,
59
57
    file_kind,
 
58
    has_symlinks,
60
59
    pathjoin,
61
60
    sha_file,
62
61
    splitpath,
63
 
    supports_symlinks,
64
62
    )
65
 
from .progress import ProgressPhase
66
 
from .tree import (
67
 
    InterTree,
68
 
    TreeChange,
 
63
from brzlib.progress import ProgressPhase
 
64
from brzlib.symbol_versioning import (
 
65
    deprecated_function,
 
66
    deprecated_in,
 
67
    deprecated_method,
69
68
    )
70
69
 
71
70
 
72
71
ROOT_PARENT = "root-parent"
73
72
 
74
 
 
75
73
def unique_add(map, key, value):
76
74
    if key in map:
77
75
        raise DuplicateKey(key=key)
78
76
    map[key] = value
79
77
 
80
78
 
 
79
 
81
80
class _TransformResults(object):
82
81
    def __init__(self, modified_paths, rename_count):
83
82
        object.__init__(self)
88
87
class TreeTransformBase(object):
89
88
    """The base class for TreeTransform and its kin."""
90
89
 
91
 
    def __init__(self, tree, pb=None, case_sensitive=True):
 
90
    def __init__(self, tree, pb=None,
 
91
                 case_sensitive=True):
92
92
        """Constructor.
93
93
 
94
94
        :param tree: The tree that will be transformed, but not necessarily
127
127
        # Mapping trans_id -> path in old tree
128
128
        self._tree_id_paths = {}
129
129
        # The trans_id that will be used as the tree root
130
 
        if tree.is_versioned(''):
131
 
            self._new_root = self.trans_id_tree_path('')
 
130
        root_id = tree.get_root_id()
 
131
        if root_id is not None:
 
132
            self._new_root = self.trans_id_tree_file_id(root_id)
132
133
        else:
133
134
            self._new_root = None
134
135
        # Indicator of whether the transform has been applied
169
170
    def _assign_id(self):
170
171
        """Produce a new tranform id"""
171
172
        new_id = "new-%s" % self._id_number
172
 
        self._id_number += 1
 
173
        self._id_number +=1
173
174
        return new_id
174
175
 
175
176
    def create_path(self, name, parent):
212
213
        # the physical root needs a new transaction id
213
214
        self._tree_path_ids.pop("")
214
215
        self._tree_id_paths.pop(old_root)
215
 
        self._new_root = self.trans_id_tree_path('')
 
216
        self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
216
217
        if parent == old_root:
217
218
            parent = self._new_root
218
219
        self.adjust_path(name, parent, old_root)
232
233
        irrelevant.
233
234
 
234
235
        """
235
 
        new_roots = [k for k, v in self._new_parent.items()
236
 
                     if v == ROOT_PARENT]
 
236
        new_roots = [k for k, v in self._new_parent.iteritems() if v ==
 
237
                     ROOT_PARENT]
237
238
        if len(new_roots) < 1:
238
239
            return
239
240
        if len(new_roots) != 1:
253
254
            self.unversion_file(old_new_root)
254
255
        # if, at this stage, root still has an old file_id, zap it so we can
255
256
        # stick a new one in.
256
 
        if (self.tree_file_id(self._new_root) is not None
257
 
                and self._new_root not in self._removed_id):
 
257
        if (self.tree_file_id(self._new_root) is not None and
 
258
            self._new_root not in self._removed_id):
258
259
            self.unversion_file(self._new_root)
259
260
        if file_id is not None:
260
261
            self.version_file(file_id, self._new_root)
281
282
        del self._new_parent[old_new_root]
282
283
        del self._new_name[old_new_root]
283
284
 
 
285
    def trans_id_tree_file_id(self, inventory_id):
 
286
        """Determine the transaction id of a working tree file.
 
287
 
 
288
        This reflects only files that already exist, not ones that will be
 
289
        added by transactions.
 
290
        """
 
291
        if inventory_id is None:
 
292
            raise ValueError('None is not a valid file id')
 
293
        path = self._tree.id2path(inventory_id)
 
294
        return self.trans_id_tree_path(path)
 
295
 
284
296
    def trans_id_file_id(self, file_id):
285
297
        """Determine or set the transaction id associated with a file ID.
286
298
        A new id is only created for file_ids that were never present.  If
293
305
            return self._r_new_id[file_id]
294
306
        else:
295
307
            try:
296
 
                path = self._tree.id2path(file_id)
297
 
            except errors.NoSuchId:
 
308
                self._tree.iter_entries_by_dir([file_id]).next()
 
309
            except StopIteration:
298
310
                if file_id in self._non_present_ids:
299
311
                    return self._non_present_ids[file_id]
300
312
                else:
302
314
                    self._non_present_ids[file_id] = trans_id
303
315
                    return trans_id
304
316
            else:
305
 
                return self.trans_id_tree_path(path)
 
317
                return self.trans_id_tree_file_id(file_id)
306
318
 
307
319
    def trans_id_tree_path(self, path):
308
320
        """Determine (and maybe set) the transaction ID for a tree path."""
419
431
        changed_ids.update(changed_kind)
420
432
        # To find entries with changed parent_ids, find parents which existed,
421
433
        # but changed file_id.
 
434
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
422
435
        # Now add all their children to the set.
423
436
        for parent_trans_id in new_file_id:
424
437
            changed_ids.update(self.iter_tree_children(parent_trans_id))
438
451
        else:
439
452
            return self.tree_kind(trans_id)
440
453
 
441
 
    def tree_path(self, trans_id):
442
 
        """Determine the tree path associated with the trans_id."""
443
 
        return self._tree_id_paths.get(trans_id)
444
 
 
445
454
    def tree_file_id(self, trans_id):
446
455
        """Determine the file id associated with the trans_id in the tree"""
447
 
        path = self.tree_path(trans_id)
448
 
        if path is None:
 
456
        try:
 
457
            path = self._tree_id_paths[trans_id]
 
458
        except KeyError:
 
459
            # the file is a new, unversioned file, or invalid trans_id
449
460
            return None
450
461
        # the file is old; the old id is still valid
451
462
        if self._new_root == trans_id:
452
 
            return self._tree.path2id('')
 
463
            return self._tree.get_root_id()
453
464
        return self._tree.path2id(path)
454
465
 
455
466
    def final_file_id(self, trans_id):
473
484
        file_id = self.tree_file_id(trans_id)
474
485
        if file_id is not None:
475
486
            return file_id
476
 
        for key, value in self._non_present_ids.items():
 
487
        for key, value in self._non_present_ids.iteritems():
477
488
            if value == trans_id:
478
489
                return key
479
490
 
503
514
        Only new paths and parents of tree files with assigned ids are used.
504
515
        """
505
516
        by_parent = {}
506
 
        items = list(self._new_parent.items())
507
 
        items.extend((t, self.final_parent(t))
508
 
                     for t in list(self._tree_id_paths))
 
517
        items = list(self._new_parent.iteritems())
 
518
        items.extend((t, self.final_parent(t)) for t in
 
519
                      self._tree_id_paths.keys())
509
520
        for trans_id, parent_id in items:
510
521
            if parent_id not in by_parent:
511
522
                by_parent[parent_id] = set()
549
560
        Active parents are those which gain children, and those which are
550
561
        removed.  This is a necessary first step in detecting conflicts.
551
562
        """
552
 
        parents = list(self.by_parent())
 
563
        parents = self.by_parent().keys()
553
564
        parents.extend([t for t in self._removed_contents if
554
565
                        self.tree_kind(t) == 'directory'])
555
566
        for trans_id in self._removed_id:
556
 
            path = self.tree_path(trans_id)
557
 
            if path is not None:
558
 
                if self._tree.stored_kind(path) == 'directory':
 
567
            file_id = self.tree_file_id(trans_id)
 
568
            if file_id is not None:
 
569
                if self._tree.stored_kind(file_id) == 'directory':
559
570
                    parents.append(trans_id)
560
571
            elif self.tree_kind(trans_id) == 'directory':
561
572
                parents.append(trans_id)
598
609
 
599
610
        :param name: The basename of the file.
600
611
 
601
 
        :param target_id: The directory trans_id where the backup should
 
612
        :param target_id: The directory trans_id where the backup should 
602
613
            be placed.
603
614
        """
604
615
        known_children = self.by_parent().get(target_id, [])
628
639
    def _unversioned_parents(self, by_parent):
629
640
        """If parent directories are versioned, children must be versioned."""
630
641
        conflicts = []
631
 
        for parent_id, children in by_parent.items():
 
642
        for parent_id, children in by_parent.iteritems():
632
643
            if parent_id == ROOT_PARENT:
633
644
                continue
634
645
            if self.final_file_id(parent_id) is not None:
636
647
            for child_id in children:
637
648
                if self.final_file_id(child_id) is not None:
638
649
                    conflicts.append(('unversioned parent', parent_id))
639
 
                    break
 
650
                    break;
640
651
        return conflicts
641
652
 
642
653
    def _improper_versioning(self):
645
656
        However, existing entries with no contents are okay.
646
657
        """
647
658
        conflicts = []
648
 
        for trans_id in self._new_id:
 
659
        for trans_id in self._new_id.iterkeys():
649
660
            kind = self.final_kind(trans_id)
650
 
            if kind == 'symlink' and not self._tree.supports_symlinks():
651
 
                # Ignore symlinks as they are not supported on this platform
652
 
                continue
653
661
            if kind is None:
654
662
                conflicts.append(('versioning no contents', trans_id))
655
663
                continue
656
 
            if not self._tree.versionable_kind(kind):
 
664
            if not inventory.InventoryEntry.versionable_kind(kind):
657
665
                conflicts.append(('versioning bad kind', trans_id, kind))
658
666
        return conflicts
659
667
 
682
690
                continue
683
691
            if trans_id not in self._removed_contents:
684
692
                conflicts.append(('overwrite', trans_id,
685
 
                                  self.final_name(trans_id)))
 
693
                                 self.final_name(trans_id)))
686
694
        return conflicts
687
695
 
688
696
    def _duplicate_entries(self, by_parent):
690
698
        conflicts = []
691
699
        if (self._new_name, self._new_parent) == ({}, {}):
692
700
            return conflicts
693
 
        for children in by_parent.values():
 
701
        for children in by_parent.itervalues():
694
702
            name_ids = []
695
703
            for child_tid in children:
696
704
                name = self.final_name(child_tid)
709
717
                    continue
710
718
                if name == last_name:
711
719
                    conflicts.append(('duplicate', last_trans_id, trans_id,
712
 
                                      name))
 
720
                    name))
713
721
                last_name = name
714
722
                last_trans_id = trans_id
715
723
        return conflicts
717
725
    def _duplicate_ids(self):
718
726
        """Each inventory id may only be used once"""
719
727
        conflicts = []
720
 
        try:
721
 
            all_ids = self._tree.all_file_ids()
722
 
        except errors.UnsupportedOperation:
723
 
            # it's okay for non-file-id trees to raise UnsupportedOperation.
724
 
            return []
725
728
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
726
729
                                self._removed_id))
 
730
        all_ids = self._tree.all_file_ids()
727
731
        active_tree_ids = all_ids.difference(removed_tree_ids)
728
 
        for trans_id, file_id in self._new_id.items():
 
732
        for trans_id, file_id in self._new_id.iteritems():
729
733
            if file_id in active_tree_ids:
730
 
                path = self._tree.id2path(file_id)
731
 
                old_trans_id = self.trans_id_tree_path(path)
 
734
                old_trans_id = self.trans_id_tree_file_id(file_id)
732
735
                conflicts.append(('duplicate id', old_trans_id, trans_id))
733
736
        return conflicts
734
737
 
735
738
    def _parent_type_conflicts(self, by_parent):
736
739
        """Children must have a directory parent"""
737
740
        conflicts = []
738
 
        for parent_id, children in by_parent.items():
 
741
        for parent_id, children in by_parent.iteritems():
739
742
            if parent_id == ROOT_PARENT:
740
743
                continue
741
744
            no_children = True
765
768
            if new_executability:
766
769
                umask = os.umask(0)
767
770
                os.umask(umask)
768
 
                to_mode = current_mode | (0o100 & ~umask)
 
771
                to_mode = current_mode | (0100 & ~umask)
769
772
                # Enable x-bit for others only if they can read it.
770
 
                if current_mode & 0o004:
771
 
                    to_mode |= 0o001 & ~umask
772
 
                if current_mode & 0o040:
773
 
                    to_mode |= 0o010 & ~umask
 
773
                if current_mode & 0004:
 
774
                    to_mode |= 0001 & ~umask
 
775
                if current_mode & 0040:
 
776
                    to_mode |= 0010 & ~umask
774
777
            else:
775
 
                to_mode = current_mode & ~0o111
 
778
                to_mode = current_mode & ~0111
776
779
            osutils.chmod_if_possible(abspath, to_mode)
777
780
 
778
781
    def _new_entry(self, name, parent_id, file_id):
870
873
    def _affected_ids(self):
871
874
        """Return the set of transform ids affected by the transform"""
872
875
        trans_ids = set(self._removed_id)
873
 
        trans_ids.update(self._new_id)
 
876
        trans_ids.update(self._new_id.keys())
874
877
        trans_ids.update(self._removed_contents)
875
 
        trans_ids.update(self._new_contents)
876
 
        trans_ids.update(self._new_executability)
877
 
        trans_ids.update(self._new_name)
878
 
        trans_ids.update(self._new_parent)
 
878
        trans_ids.update(self._new_contents.keys())
 
879
        trans_ids.update(self._new_executability.keys())
 
880
        trans_ids.update(self._new_name.keys())
 
881
        trans_ids.update(self._new_parent.keys())
879
882
        return trans_ids
880
883
 
881
884
    def _get_file_id_maps(self):
894
897
                to_trans_ids[to_file_id] = trans_id
895
898
        return from_trans_ids, to_trans_ids
896
899
 
897
 
    def _from_file_data(self, from_trans_id, from_versioned, from_path):
 
900
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
898
901
        """Get data about a file in the from (tree) state
899
902
 
900
903
        Return a (name, parent, kind, executable) tuple
902
905
        from_path = self._tree_id_paths.get(from_trans_id)
903
906
        if from_versioned:
904
907
            # get data from working tree if versioned
905
 
            from_entry = next(self._tree.iter_entries_by_dir(
906
 
                specific_files=[from_path]))[1]
 
908
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
907
909
            from_name = from_entry.name
908
910
            from_parent = from_entry.parent_id
909
911
        else:
956
958
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
957
959
        results = []
958
960
        # Now iterate through all active file_ids
959
 
        for file_id in set(from_trans_ids).union(to_trans_ids):
 
961
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
960
962
            modified = False
961
963
            from_trans_id = from_trans_ids.get(file_id)
962
964
            # find file ids, and determine versioning state
972
974
            else:
973
975
                to_versioned = True
974
976
 
 
977
            from_name, from_parent, from_kind, from_executable = \
 
978
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
979
 
 
980
            to_name, to_parent, to_kind, to_executable = \
 
981
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
982
 
975
983
            if not from_versioned:
976
984
                from_path = None
977
985
            else:
980
988
                to_path = None
981
989
            else:
982
990
                to_path = final_paths.get_path(to_trans_id)
983
 
 
984
 
            from_name, from_parent, from_kind, from_executable = \
985
 
                self._from_file_data(from_trans_id, from_versioned, from_path)
986
 
 
987
 
            to_name, to_parent, to_kind, to_executable = \
988
 
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
989
 
 
990
991
            if from_kind != to_kind:
991
992
                modified = True
992
993
            elif to_kind in ('file', 'symlink') and (
993
 
                    to_trans_id != from_trans_id
994
 
                    or to_trans_id in self._new_contents):
 
994
                to_trans_id != from_trans_id or
 
995
                to_trans_id in self._new_contents):
995
996
                modified = True
996
 
            if (not modified and from_versioned == to_versioned
997
 
                and from_parent == to_parent and from_name == to_name
998
 
                    and from_executable == to_executable):
 
997
            if (not modified and from_versioned == to_versioned and
 
998
                from_parent==to_parent and from_name == to_name and
 
999
                from_executable == to_executable):
999
1000
                continue
1000
 
            results.append(
1001
 
                TreeChange(
1002
 
                    file_id, (from_path, to_path), modified,
1003
 
                    (from_versioned, to_versioned),
1004
 
                    (from_parent, to_parent),
1005
 
                    (from_name, to_name),
1006
 
                    (from_kind, to_kind),
1007
 
                    (from_executable, to_executable)))
1008
 
 
1009
 
        def path_key(c):
1010
 
            return (c.path[0] or '', c.path[1] or '')
1011
 
        return iter(sorted(results, key=path_key))
 
1001
            results.append((file_id, (from_path, to_path), modified,
 
1002
                   (from_versioned, to_versioned),
 
1003
                   (from_parent, to_parent),
 
1004
                   (from_name, to_name),
 
1005
                   (from_kind, to_kind),
 
1006
                   (from_executable, to_executable)))
 
1007
        return iter(sorted(results, key=lambda x:x[1]))
1012
1008
 
1013
1009
    def get_preview_tree(self):
1014
1010
        """Return a tree representing the result of the transform.
1060
1056
                parent_ids.extend(merge_parents)
1061
1057
        if self._tree.get_revision_id() != last_rev_id:
1062
1058
            raise ValueError('TreeTransform not based on branch basis: %s' %
1063
 
                             self._tree.get_revision_id().decode('utf-8'))
 
1059
                             self._tree.get_revision_id())
1064
1060
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
1065
1061
        builder = branch.get_commit_builder(parent_ids,
1066
1062
                                            timestamp=timestamp,
1077
1073
        return revision_id
1078
1074
 
1079
1075
    def _text_parent(self, trans_id):
1080
 
        path = self.tree_path(trans_id)
 
1076
        file_id = self.tree_file_id(trans_id)
1081
1077
        try:
1082
 
            if path is None or self._tree.kind(path) != 'file':
 
1078
            if file_id is None or self._tree.kind(file_id) != 'file':
1083
1079
                return None
1084
1080
        except errors.NoSuchFile:
1085
1081
            return None
1086
 
        return path
 
1082
        return file_id
1087
1083
 
1088
1084
    def _get_parents_texts(self, trans_id):
1089
1085
        """Get texts for compression parents of this file."""
1090
 
        path = self._text_parent(trans_id)
1091
 
        if path is None:
 
1086
        file_id = self._text_parent(trans_id)
 
1087
        if file_id is None:
1092
1088
            return ()
1093
 
        return (self._tree.get_file_text(path),)
 
1089
        return (self._tree.get_file_text(file_id),)
1094
1090
 
1095
1091
    def _get_parents_lines(self, trans_id):
1096
1092
        """Get lines for compression parents of this file."""
1097
 
        path = self._text_parent(trans_id)
1098
 
        if path is None:
 
1093
        file_id = self._text_parent(trans_id)
 
1094
        if file_id is None:
1099
1095
            return ()
1100
 
        return (self._tree.get_file_lines(path),)
 
1096
        return (self._tree.get_file_lines(file_id),)
1101
1097
 
1102
1098
    def serialize(self, serializer):
1103
1099
        """Serialize this TreeTransform.
1104
1100
 
1105
1101
        :param serializer: A Serialiser like pack.ContainerSerializer.
1106
1102
        """
1107
 
        new_name = {k.encode('utf-8'): v.encode('utf-8')
1108
 
                    for k, v in self._new_name.items()}
1109
 
        new_parent = {k.encode('utf-8'): v.encode('utf-8')
1110
 
                      for k, v in self._new_parent.items()}
1111
 
        new_id = {k.encode('utf-8'): v
1112
 
                  for k, v in self._new_id.items()}
1113
 
        new_executability = {k.encode('utf-8'): int(v)
1114
 
                             for k, v in self._new_executability.items()}
1115
 
        tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1116
 
                         for k, v in self._tree_path_ids.items()}
1117
 
        non_present_ids = {k: v.encode('utf-8')
1118
 
                           for k, v in self._non_present_ids.items()}
1119
 
        removed_contents = [trans_id.encode('utf-8')
1120
 
                            for trans_id in self._removed_contents]
1121
 
        removed_id = [trans_id.encode('utf-8')
1122
 
                      for trans_id in self._removed_id]
 
1103
        new_name = dict((k, v.encode('utf-8')) for k, v in
 
1104
                        self._new_name.items())
 
1105
        new_executability = dict((k, int(v)) for k, v in
 
1106
                                 self._new_executability.items())
 
1107
        tree_path_ids = dict((k.encode('utf-8'), v)
 
1108
                             for k, v in self._tree_path_ids.items())
1123
1109
        attribs = {
1124
 
            b'_id_number': self._id_number,
1125
 
            b'_new_name': new_name,
1126
 
            b'_new_parent': new_parent,
1127
 
            b'_new_executability': new_executability,
1128
 
            b'_new_id': new_id,
1129
 
            b'_tree_path_ids': tree_path_ids,
1130
 
            b'_removed_id': removed_id,
1131
 
            b'_removed_contents': removed_contents,
1132
 
            b'_non_present_ids': non_present_ids,
 
1110
            '_id_number': self._id_number,
 
1111
            '_new_name': new_name,
 
1112
            '_new_parent': self._new_parent,
 
1113
            '_new_executability': new_executability,
 
1114
            '_new_id': self._new_id,
 
1115
            '_tree_path_ids': tree_path_ids,
 
1116
            '_removed_id': list(self._removed_id),
 
1117
            '_removed_contents': list(self._removed_contents),
 
1118
            '_non_present_ids': self._non_present_ids,
1133
1119
            }
1134
1120
        yield serializer.bytes_record(bencode.bencode(attribs),
1135
 
                                      ((b'attribs',),))
1136
 
        for trans_id, kind in sorted(self._new_contents.items()):
 
1121
                                      (('attribs',),))
 
1122
        for trans_id, kind in self._new_contents.items():
1137
1123
            if kind == 'file':
1138
 
                with open(self._limbo_name(trans_id), 'rb') as cur_file:
1139
 
                    lines = cur_file.readlines()
 
1124
                lines = osutils.chunks_to_lines(
 
1125
                    self._read_file_chunks(trans_id))
1140
1126
                parents = self._get_parents_lines(trans_id)
1141
1127
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1142
 
                content = b''.join(mpdiff.to_patch())
 
1128
                content = ''.join(mpdiff.to_patch())
1143
1129
            if kind == 'directory':
1144
 
                content = b''
 
1130
                content = ''
1145
1131
            if kind == 'symlink':
1146
1132
                content = self._read_symlink_target(trans_id)
1147
 
                if not isinstance(content, bytes):
1148
 
                    content = content.encode('utf-8')
1149
 
            yield serializer.bytes_record(
1150
 
                content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
 
1133
            yield serializer.bytes_record(content, ((trans_id, kind),))
1151
1134
 
1152
1135
    def deserialize(self, records):
1153
1136
        """Deserialize a stored TreeTransform.
1155
1138
        :param records: An iterable of (names, content) tuples, as per
1156
1139
            pack.ContainerPushParser.
1157
1140
        """
1158
 
        names, content = next(records)
 
1141
        names, content = records.next()
1159
1142
        attribs = bencode.bdecode(content)
1160
 
        self._id_number = attribs[b'_id_number']
1161
 
        self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1162
 
                          for k, v in attribs[b'_new_name'].items()}
1163
 
        self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1164
 
                            for k, v in attribs[b'_new_parent'].items()}
1165
 
        self._new_executability = {
1166
 
            k.decode('utf-8'): bool(v)
1167
 
            for k, v in attribs[b'_new_executability'].items()}
1168
 
        self._new_id = {k.decode('utf-8'): v
1169
 
                        for k, v in attribs[b'_new_id'].items()}
1170
 
        self._r_new_id = {v: k for k, v in self._new_id.items()}
 
1143
        self._id_number = attribs['_id_number']
 
1144
        self._new_name = dict((k, v.decode('utf-8'))
 
1145
                            for k, v in attribs['_new_name'].items())
 
1146
        self._new_parent = attribs['_new_parent']
 
1147
        self._new_executability = dict((k, bool(v)) for k, v in
 
1148
            attribs['_new_executability'].items())
 
1149
        self._new_id = attribs['_new_id']
 
1150
        self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1171
1151
        self._tree_path_ids = {}
1172
1152
        self._tree_id_paths = {}
1173
 
        for bytepath, trans_id in attribs[b'_tree_path_ids'].items():
 
1153
        for bytepath, trans_id in attribs['_tree_path_ids'].items():
1174
1154
            path = bytepath.decode('utf-8')
1175
 
            trans_id = trans_id.decode('utf-8')
1176
1155
            self._tree_path_ids[path] = trans_id
1177
1156
            self._tree_id_paths[trans_id] = path
1178
 
        self._removed_id = {trans_id.decode('utf-8')
1179
 
                            for trans_id in attribs[b'_removed_id']}
1180
 
        self._removed_contents = set(
1181
 
            trans_id.decode('utf-8')
1182
 
            for trans_id in attribs[b'_removed_contents'])
1183
 
        self._non_present_ids = {
1184
 
            k: v.decode('utf-8')
1185
 
            for k, v in attribs[b'_non_present_ids'].items()}
 
1157
        self._removed_id = set(attribs['_removed_id'])
 
1158
        self._removed_contents = set(attribs['_removed_contents'])
 
1159
        self._non_present_ids = attribs['_non_present_ids']
1186
1160
        for ((trans_id, kind),), content in records:
1187
 
            trans_id = trans_id.decode('utf-8')
1188
 
            kind = kind.decode('ascii')
1189
1161
            if kind == 'file':
1190
1162
                mpdiff = multiparent.MultiParent.from_patch(content)
1191
1163
                lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1195
1167
            if kind == 'symlink':
1196
1168
                self.create_symlink(content.decode('utf-8'), trans_id)
1197
1169
 
1198
 
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1199
 
        """Schedule creation of a new file.
1200
 
 
1201
 
        :seealso: new_file.
1202
 
 
1203
 
        :param contents: an iterator of strings, all of which will be written
1204
 
            to the target destination.
1205
 
        :param trans_id: TreeTransform handle
1206
 
        :param mode_id: If not None, force the mode of the target file to match
1207
 
            the mode of the object referenced by mode_id.
1208
 
            Otherwise, we will try to preserve mode bits of an existing file.
1209
 
        :param sha1: If the sha1 of this content is already known, pass it in.
1210
 
            We can use it to prevent future sha1 computations.
1211
 
        """
1212
 
        raise NotImplementedError(self.create_file)
1213
 
 
1214
 
    def create_directory(self, trans_id):
1215
 
        """Schedule creation of a new directory.
1216
 
 
1217
 
        See also new_directory.
1218
 
        """
1219
 
        raise NotImplementedError(self.create_directory)
1220
 
 
1221
 
    def create_symlink(self, target, trans_id):
1222
 
        """Schedule creation of a new symbolic link.
1223
 
 
1224
 
        target is a bytestring.
1225
 
        See also new_symlink.
1226
 
        """
1227
 
        raise NotImplementedError(self.create_symlink)
1228
 
 
1229
 
    def create_hardlink(self, path, trans_id):
1230
 
        """Schedule creation of a hard link"""
1231
 
        raise NotImplementedError(self.create_hardlink)
1232
 
 
1233
 
    def cancel_creation(self, trans_id):
1234
 
        """Cancel the creation of new file contents."""
1235
 
        raise NotImplementedError(self.cancel_creation)
1236
 
 
1237
1170
 
1238
1171
class DiskTreeTransform(TreeTransformBase):
1239
1172
    """Tree transform storing its contents on disk."""
1240
1173
 
1241
 
    def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
 
1174
    def __init__(self, tree, limbodir, pb=None,
 
1175
                 case_sensitive=True):
1242
1176
        """Constructor.
1243
1177
        :param tree: The tree that will be transformed, but not necessarily
1244
1178
            the output tree.
1262
1196
        # List of transform ids that need to be renamed from limbo into place
1263
1197
        self._needs_rename = set()
1264
1198
        self._creation_mtime = None
1265
 
        self._create_symlinks = osutils.supports_symlinks(self._limbodir)
1266
1199
 
1267
1200
    def finalize(self):
1268
1201
        """Release the working tree lock, if held, clean up limbo dir.
1273
1206
        if self._tree is None:
1274
1207
            return
1275
1208
        try:
1276
 
            limbo_paths = list(self._limbo_files.values())
1277
 
            limbo_paths.extend(self._possibly_stale_limbo_files)
1278
 
            limbo_paths.sort(reverse=True)
 
1209
            limbo_paths = self._limbo_files.values() + list(
 
1210
                self._possibly_stale_limbo_files)
 
1211
            limbo_paths = sorted(limbo_paths, reverse=True)
1279
1212
            for path in limbo_paths:
1280
1213
                try:
1281
1214
                    delete_any(path)
1282
 
                except OSError as e:
 
1215
                except OSError, e:
1283
1216
                    if e.errno != errno.ENOENT:
1284
1217
                        raise
1285
1218
                    # XXX: warn? perhaps we just got interrupted at an
1300
1233
 
1301
1234
    def _limbo_supports_executable(self):
1302
1235
        """Check if the limbo path supports the executable bit."""
1303
 
        return osutils.supports_executable(self._limbodir)
 
1236
        # FIXME: Check actual file system capabilities of limbodir
 
1237
        return osutils.supports_executable()
1304
1238
 
1305
1239
    def _limbo_name(self, trans_id):
1306
1240
        """Generate the limbo name of a file"""
1323
1257
        previous_parent = self._new_parent.get(trans_id)
1324
1258
        previous_name = self._new_name.get(trans_id)
1325
1259
        TreeTransformBase.adjust_path(self, name, parent, trans_id)
1326
 
        if (trans_id in self._limbo_files
1327
 
                and trans_id not in self._needs_rename):
 
1260
        if (trans_id in self._limbo_files and
 
1261
            trans_id not in self._needs_rename):
1328
1262
            self._rename_in_limbo([trans_id])
1329
1263
            if previous_parent != parent:
1330
1264
                self._limbo_children[previous_parent].remove(trans_id)
1378
1312
            We can use it to prevent future sha1 computations.
1379
1313
        """
1380
1314
        name = self._limbo_name(trans_id)
1381
 
        with open(name, 'wb') as f:
 
1315
        f = open(name, 'wb')
 
1316
        try:
1382
1317
            unique_add(self._new_contents, trans_id, 'file')
1383
1318
            f.writelines(contents)
 
1319
        finally:
 
1320
            f.close()
1384
1321
        self._set_mtime(name)
1385
1322
        self._set_mode(trans_id, mode_id, S_ISREG)
1386
1323
        # It is unfortunate we have to use lstat instead of fstat, but we just
1389
1326
        if sha1 is not None:
1390
1327
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1391
1328
 
 
1329
    def _read_file_chunks(self, trans_id):
 
1330
        cur_file = open(self._limbo_name(trans_id), 'rb')
 
1331
        try:
 
1332
            return cur_file.readlines()
 
1333
        finally:
 
1334
            cur_file.close()
 
1335
 
1392
1336
    def _read_symlink_target(self, trans_id):
1393
1337
        return os.readlink(self._limbo_name(trans_id))
1394
1338
 
1406
1350
        name = self._limbo_name(trans_id)
1407
1351
        try:
1408
1352
            os.link(path, name)
1409
 
        except OSError as e:
 
1353
        except OSError, e:
1410
1354
            if e.errno != errno.EPERM:
1411
1355
                raise
1412
1356
            raise errors.HardLinkNotSupported(path)
1413
1357
        try:
1414
1358
            unique_add(self._new_contents, trans_id, 'file')
1415
 
        except BaseException:
 
1359
        except:
1416
1360
            # Clean up the file, it never got registered so
1417
1361
            # TreeTransform.finalize() won't clean it up.
1418
1362
            os.unlink(name)
1432
1376
        target is a bytestring.
1433
1377
        See also new_symlink.
1434
1378
        """
1435
 
        if self._create_symlinks:
 
1379
        if has_symlinks():
1436
1380
            os.symlink(target, self._limbo_name(trans_id))
 
1381
            unique_add(self._new_contents, trans_id, 'symlink')
1437
1382
        else:
1438
1383
            try:
1439
1384
                path = FinalPaths(self).get_path(trans_id)
1440
1385
            except KeyError:
1441
1386
                path = None
1442
 
            trace.warning(
1443
 
                'Unable to create symlink "%s" on this filesystem.' % (path,))
1444
 
        # We add symlink to _new_contents even if they are unsupported
1445
 
        # and not created. These entries are subsequently used to avoid
1446
 
        # conflicts on platforms that don't support symlink
1447
 
        unique_add(self._new_contents, trans_id, 'symlink')
 
1387
            raise UnableCreateSymlink(path=path)
1448
1388
 
1449
1389
    def cancel_creation(self, trans_id):
1450
1390
        """Cancel the creation of new file contents."""
1462
1402
 
1463
1403
    def new_orphan(self, trans_id, parent_id):
1464
1404
        conf = self._tree.get_config_stack()
1465
 
        handle_orphan = conf.get('transform.orphan_policy')
 
1405
        handle_orphan = conf.get('bzr.transform.orphan_policy')
1466
1406
        handle_orphan(self, trans_id, parent_id)
1467
1407
 
1468
1408
 
1490
1430
def move_orphan(tt, orphan_id, parent_id):
1491
1431
    """See TreeTransformBase.new_orphan.
1492
1432
 
1493
 
    This creates a new orphan in the `brz-orphans` dir at the root of the
 
1433
    This creates a new orphan in the `bzr-orphans` dir at the root of the
1494
1434
    `TreeTransform`.
1495
1435
 
1496
1436
    :param tt: The TreeTransform orphaning `trans_id`.
1500
1440
    :param parent_id: The orphan parent trans id.
1501
1441
    """
1502
1442
    # Add the orphan dir if it doesn't exist
1503
 
    orphan_dir_basename = 'brz-orphans'
 
1443
    orphan_dir_basename = 'bzr-orphans'
1504
1444
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
1505
1445
    if tt.final_kind(od_id) is None:
1506
1446
        tt.create_directory(od_id)
1523
1463
 
1524
1464
orphaning_registry = registry.Registry()
1525
1465
orphaning_registry.register(
1526
 
    u'conflict', refuse_orphan,
 
1466
    'conflict', refuse_orphan,
1527
1467
    'Leave orphans in place and create a conflict on the directory.')
1528
1468
orphaning_registry.register(
1529
 
    u'move', move_orphan,
1530
 
    'Move orphans into the brz-orphans directory.')
1531
 
orphaning_registry._set_default_key(u'conflict')
 
1469
    'move', move_orphan,
 
1470
    'Move orphans into the bzr-orphans directory.')
 
1471
orphaning_registry._set_default_key('conflict')
1532
1472
 
1533
1473
 
1534
1474
opt_transform_orphan = _mod_config.RegistryOption(
1535
 
    'transform.orphan_policy', orphaning_registry,
 
1475
    'bzr.transform.orphan_policy', orphaning_registry,
1536
1476
    help='Policy for orphaned files during transform operations.',
1537
1477
    invalid='warning')
1538
1478
 
1602
1542
    FileMover does not delete files until it is sure that a rollback will not
1603
1543
    happen.
1604
1544
    """
1605
 
 
1606
1545
    def __init__(self, tree, pb=None):
1607
1546
        """Note: a tree_write lock is taken on the tree.
1608
1547
 
1610
1549
        TreeTransform.apply() called).
1611
1550
        """
1612
1551
        tree.lock_tree_write()
 
1552
 
1613
1553
        try:
1614
1554
            limbodir = urlutils.local_path_from_url(
1615
1555
                tree._transport.abspath('limbo'))
1621
1561
            osutils.ensure_empty_directory_exists(
1622
1562
                deletiondir,
1623
1563
                errors.ExistingPendingDeletion)
1624
 
        except BaseException:
 
1564
        except:
1625
1565
            tree.unlock()
1626
1566
            raise
1627
1567
 
1679
1619
            return
1680
1620
        try:
1681
1621
            mode = os.stat(self._tree.abspath(old_path)).st_mode
1682
 
        except OSError as e:
 
1622
        except OSError, e:
1683
1623
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
1684
1624
                # Either old_path doesn't exist, or the parent of the
1685
1625
                # target is not a directory (but will be one eventually)
1699
1639
            return
1700
1640
        try:
1701
1641
            children = os.listdir(self._tree.abspath(path))
1702
 
        except OSError as e:
1703
 
            if not (osutils._is_error_enotdir(e) or
1704
 
                    e.errno in (errno.ENOENT, errno.ESRCH)):
 
1642
        except OSError, e:
 
1643
            if not (osutils._is_error_enotdir(e)
 
1644
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
1705
1645
                raise
1706
1646
            return
1707
1647
 
1738
1678
                # if it is already associated with this trans_id.
1739
1679
                elif self._case_sensitive_target:
1740
1680
                    if (self._limbo_children_names[parent].get(filename)
1741
 
                            in (trans_id, None)):
 
1681
                        in (trans_id, None)):
1742
1682
                        use_direct_path = True
1743
1683
                else:
1744
 
                    for l_filename, l_trans_id in (
1745
 
                            self._limbo_children_names[parent].items()):
 
1684
                    for l_filename, l_trans_id in\
 
1685
                        self._limbo_children_names[parent].iteritems():
1746
1686
                        if l_trans_id == trans_id:
1747
1687
                            continue
1748
1688
                        if l_filename.lower() == filename.lower():
1758
1698
        self._limbo_children_names[parent][filename] = trans_id
1759
1699
        return limbo_name
1760
1700
 
 
1701
 
1761
1702
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1762
1703
        """Apply all changes to the inventory and filesystem.
1763
1704
 
1776
1717
            hook(self._tree, self)
1777
1718
        if not no_conflicts:
1778
1719
            self._check_malformed()
1779
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1720
        child_pb = ui.ui_factory.nested_progress_bar()
 
1721
        try:
1780
1722
            if precomputed_delta is None:
1781
1723
                child_pb.update(gettext('Apply phase'), 0, 2)
1782
1724
                inventory_delta = self._generate_inventory_delta()
1793
1735
                self._apply_removals(mover)
1794
1736
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1795
1737
                modified_paths = self._apply_insertions(mover)
1796
 
            except BaseException:
 
1738
            except:
1797
1739
                mover.rollback()
1798
1740
                raise
1799
1741
            else:
1800
1742
                mover.apply_deletions()
 
1743
        finally:
 
1744
            child_pb.finished()
1801
1745
        if self.final_file_id(self.root) is None:
1802
1746
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1803
1747
        self._tree.apply_inventory_delta(inventory_delta)
1809
1753
    def _generate_inventory_delta(self):
1810
1754
        """Generate an inventory delta for the current transform."""
1811
1755
        inventory_delta = []
 
1756
        child_pb = ui.ui_factory.nested_progress_bar()
1812
1757
        new_paths = self._inventory_altered()
1813
1758
        total_entries = len(new_paths) + len(self._removed_id)
1814
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1759
        try:
1815
1760
            for num, trans_id in enumerate(self._removed_id):
1816
1761
                if (num % 10) == 0:
1817
 
                    child_pb.update(gettext('removing file'),
1818
 
                                    num, total_entries)
 
1762
                    child_pb.update(gettext('removing file'), num, total_entries)
1819
1763
                if trans_id == self._new_root:
1820
 
                    file_id = self._tree.path2id('')
 
1764
                    file_id = self._tree.get_root_id()
1821
1765
                else:
1822
1766
                    file_id = self.tree_file_id(trans_id)
1823
1767
                # File-id isn't really being deleted, just moved
1827
1771
                inventory_delta.append((path, None, file_id, None))
1828
1772
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1829
1773
                                     new_paths)
 
1774
            entries = self._tree.iter_entries_by_dir(
 
1775
                new_path_file_ids.values())
 
1776
            old_paths = dict((e.file_id, p) for p, e in entries)
 
1777
            final_kinds = {}
1830
1778
            for num, (path, trans_id) in enumerate(new_paths):
1831
1779
                if (num % 10) == 0:
1832
1780
                    child_pb.update(gettext('adding file'),
1834
1782
                file_id = new_path_file_ids[trans_id]
1835
1783
                if file_id is None:
1836
1784
                    continue
 
1785
                needs_entry = False
1837
1786
                kind = self.final_kind(trans_id)
1838
1787
                if kind is None:
1839
 
                    kind = self._tree.stored_kind(self._tree.id2path(file_id))
 
1788
                    kind = self._tree.stored_kind(file_id)
1840
1789
                parent_trans_id = self.final_parent(trans_id)
1841
1790
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1842
1791
                if parent_file_id is None:
1849
1798
                        None, self._new_reference_revision[trans_id])
1850
1799
                else:
1851
1800
                    new_entry = inventory.make_entry(kind,
1852
 
                                                     self.final_name(trans_id),
1853
 
                                                     parent_file_id, file_id)
1854
 
                try:
1855
 
                    old_path = self._tree.id2path(new_entry.file_id)
1856
 
                except errors.NoSuchId:
1857
 
                    old_path = None
 
1801
                        self.final_name(trans_id),
 
1802
                        parent_file_id, file_id)
 
1803
                old_path = old_paths.get(new_entry.file_id)
1858
1804
                new_executability = self._new_executability.get(trans_id)
1859
1805
                if new_executability is not None:
1860
1806
                    new_entry.executable = new_executability
1861
1807
                inventory_delta.append(
1862
1808
                    (old_path, path, new_entry.file_id, new_entry))
 
1809
        finally:
 
1810
            child_pb.finished()
1863
1811
        return inventory_delta
1864
1812
 
1865
1813
    def _apply_removals(self, mover):
1871
1819
 
1872
1820
        If inventory_delta is None, no inventory delta generation is performed.
1873
1821
        """
1874
 
        tree_paths = sorted(self._tree_path_ids.items(), reverse=True)
1875
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1822
        tree_paths = list(self._tree_path_ids.iteritems())
 
1823
        tree_paths.sort(reverse=True)
 
1824
        child_pb = ui.ui_factory.nested_progress_bar()
 
1825
        try:
1876
1826
            for num, (path, trans_id) in enumerate(tree_paths):
1877
1827
                # do not attempt to move root into a subdirectory of itself.
1878
1828
                if path == '':
1882
1832
                if trans_id in self._removed_contents:
1883
1833
                    delete_path = os.path.join(self._deletiondir, trans_id)
1884
1834
                    mover.pre_delete(full_path, delete_path)
1885
 
                elif (trans_id in self._new_name or
1886
 
                      trans_id in self._new_parent):
 
1835
                elif (trans_id in self._new_name
 
1836
                      or trans_id in self._new_parent):
1887
1837
                    try:
1888
1838
                        mover.rename(full_path, self._limbo_name(trans_id))
1889
 
                    except errors.TransformRenameFailed as e:
 
1839
                    except errors.TransformRenameFailed, e:
1890
1840
                        if e.errno != errno.ENOENT:
1891
1841
                            raise
1892
1842
                    else:
1893
1843
                        self.rename_count += 1
 
1844
        finally:
 
1845
            child_pb.finished()
1894
1846
 
1895
1847
    def _apply_insertions(self, mover):
1896
1848
        """Perform tree operations that insert directory/inventory names.
1904
1856
        """
1905
1857
        new_paths = self.new_paths(filesystem_only=True)
1906
1858
        modified_paths = []
1907
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1859
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1860
                                 new_paths)
 
1861
        child_pb = ui.ui_factory.nested_progress_bar()
 
1862
        try:
1908
1863
            for num, (path, trans_id) in enumerate(new_paths):
1909
1864
                if (num % 10) == 0:
1910
 
                    child_pb.update(gettext('adding file'),
1911
 
                                    num, len(new_paths))
 
1865
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1912
1866
                full_path = self._tree.abspath(path)
1913
1867
                if trans_id in self._needs_rename:
1914
1868
                    try:
1915
1869
                        mover.rename(self._limbo_name(trans_id), full_path)
1916
 
                    except errors.TransformRenameFailed as e:
 
1870
                    except errors.TransformRenameFailed, e:
1917
1871
                        # We may be renaming a dangling inventory id
1918
1872
                        if e.errno != errno.ENOENT:
1919
1873
                            raise
1922
1876
                    # TODO: if trans_id in self._observed_sha1s, we should
1923
1877
                    #       re-stat the final target, since ctime will be
1924
1878
                    #       updated by the change.
1925
 
                if (trans_id in self._new_contents
1926
 
                        or self.path_changed(trans_id)):
 
1879
                if (trans_id in self._new_contents or
 
1880
                    self.path_changed(trans_id)):
1927
1881
                    if trans_id in self._new_contents:
1928
1882
                        modified_paths.append(full_path)
1929
1883
                if trans_id in self._new_executability:
1932
1886
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
1933
1887
                    st = osutils.lstat(full_path)
1934
1888
                    self._observed_sha1s[trans_id] = (o_sha1, st)
 
1889
        finally:
 
1890
            child_pb.finished()
1935
1891
        for path, trans_id in new_paths:
1936
1892
            # new_paths includes stuff like workingtree conflicts. Only the
1937
1893
            # stuff in new_contents actually comes from limbo.
1956
1912
        #       problems. (we could observe start time, and finish time, and if
1957
1913
        #       it is less than eg 10% overhead, add a sleep call.)
1958
1914
        paths = FinalPaths(self)
1959
 
        for trans_id, observed in self._observed_sha1s.items():
 
1915
        for trans_id, observed in self._observed_sha1s.iteritems():
1960
1916
            path = paths.get_path(trans_id)
1961
 
            self._tree._observed_sha1(path, observed)
 
1917
            # We could get the file_id, but dirstate prefers to use the path
 
1918
            # anyway, and it is 'cheaper' to determine.
 
1919
            # file_id = self._new_id[trans_id]
 
1920
            self._tree._observed_sha1(None, path, observed)
1962
1921
 
1963
1922
 
1964
1923
class TransformPreview(DiskTreeTransform):
2001
1960
            path = self._tree_id_paths[parent_id]
2002
1961
        except KeyError:
2003
1962
            return
2004
 
        try:
2005
 
            entry = next(self._tree.iter_entries_by_dir(
2006
 
                specific_files=[path]))[1]
2007
 
        except StopIteration:
 
1963
        file_id = self.tree_file_id(parent_id)
 
1964
        if file_id is None:
2008
1965
            return
 
1966
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
2009
1967
        children = getattr(entry, 'children', {})
2010
1968
        for child in children:
2011
1969
            childpath = joinpath(path, child)
2015
1973
        raise NotImplementedError(self.new_orphan)
2016
1974
 
2017
1975
 
2018
 
class _PreviewTree(inventorytree.InventoryTree):
 
1976
class _PreviewTree(tree.InventoryTree):
2019
1977
    """Partial implementation of Tree to support show_diff_trees"""
2020
1978
 
2021
1979
    def __init__(self, transform):
2026
1984
        self._all_children_cache = {}
2027
1985
        self._path2trans_id_cache = {}
2028
1986
        self._final_name_cache = {}
2029
 
        self._iter_changes_cache = dict((c.file_id, c) for c in
 
1987
        self._iter_changes_cache = dict((c[0], c) for c in
2030
1988
                                        self._transform.iter_changes())
2031
1989
 
2032
 
    def supports_tree_reference(self):
2033
 
        # TODO(jelmer): Support tree references in _PreviewTree.
2034
 
        # return self._transform._tree.supports_tree_reference()
2035
 
        return False
2036
 
 
2037
1990
    def _content_change(self, file_id):
2038
1991
        """Return True if the content of this file changed"""
2039
1992
        changes = self._iter_changes_cache.get(file_id)
2040
 
        return (changes is not None and changes.changed_content)
 
1993
        # changes[2] is true if the file content changed.  See
 
1994
        # InterTree.iter_changes.
 
1995
        return (changes is not None and changes[2])
2041
1996
 
2042
1997
    def _get_repository(self):
2043
1998
        repo = getattr(self._transform._tree, '_repository', None)
2052
2007
            except errors.NoSuchRevisionInTree:
2053
2008
                yield self._get_repository().revision_tree(revision_id)
2054
2009
 
2055
 
    def _get_file_revision(self, path, file_id, vf, tree_revision):
2056
 
        parent_keys = [
2057
 
            (file_id, t.get_file_revision(t.id2path(file_id)))
2058
 
            for t in self._iter_parent_trees()]
 
2010
    def _get_file_revision(self, file_id, vf, tree_revision):
 
2011
        parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
 
2012
                       self._iter_parent_trees()]
2059
2013
        vf.add_lines((file_id, tree_revision), parent_keys,
2060
 
                     self.get_file_lines(path))
 
2014
                     self.get_file_lines(file_id))
2061
2015
        repo = self._get_repository()
2062
2016
        base_vf = repo.texts
2063
2017
        if base_vf not in vf.fallback_versionedfiles:
2064
2018
            vf.fallback_versionedfiles.append(base_vf)
2065
2019
        return tree_revision
2066
2020
 
2067
 
    def _stat_limbo_file(self, trans_id):
 
2021
    def _stat_limbo_file(self, file_id=None, trans_id=None):
 
2022
        if trans_id is None:
 
2023
            trans_id = self._transform.trans_id_file_id(file_id)
2068
2024
        name = self._transform._limbo_name(trans_id)
2069
2025
        return os.lstat(name)
2070
2026
 
2081
2037
            executable = False
2082
2038
        else:
2083
2039
            file_id = self._transform.final_file_id(self._path2trans_id(path))
2084
 
            executable = self.is_executable(path)
 
2040
            executable = self.is_executable(file_id, path)
2085
2041
        return kind, executable, None
2086
2042
 
2087
2043
    def is_locked(self):
2089
2045
 
2090
2046
    def lock_read(self):
2091
2047
        # Perhaps in theory, this should lock the TreeTransform?
2092
 
        return lock.LogicalLockResult(self.unlock)
 
2048
        return self
2093
2049
 
2094
2050
    def unlock(self):
2095
2051
        pass
2096
2052
 
2097
2053
    @property
 
2054
    @deprecated_method(deprecated_in((2, 5, 0)))
 
2055
    def inventory(self):
 
2056
        """This Tree does not use inventory as its backing data."""
 
2057
        raise NotImplementedError(_PreviewTree.inventory)
 
2058
 
 
2059
    @property
2098
2060
    def root_inventory(self):
2099
2061
        """This Tree does not use inventory as its backing data."""
2100
2062
        raise NotImplementedError(_PreviewTree.root_inventory)
2101
2063
 
 
2064
    def get_root_id(self):
 
2065
        return self._transform.final_file_id(self._transform.root)
 
2066
 
2102
2067
    def all_file_ids(self):
2103
2068
        tree_ids = set(self._transform._tree.all_file_ids())
2104
2069
        tree_ids.difference_update(self._transform.tree_file_id(t)
2106
2071
        tree_ids.update(self._transform._new_id.values())
2107
2072
        return tree_ids
2108
2073
 
2109
 
    def all_versioned_paths(self):
2110
 
        tree_paths = set(self._transform._tree.all_versioned_paths())
2111
 
 
2112
 
        tree_paths.difference_update(
2113
 
            self._transform.trans_id_tree_path(t)
2114
 
            for t in self._transform._removed_id)
2115
 
 
2116
 
        tree_paths.update(
2117
 
            self._final_paths._determine_path(t)
2118
 
            for t in self._transform._new_id)
2119
 
 
2120
 
        return tree_paths
 
2074
    def __iter__(self):
 
2075
        return iter(self.all_file_ids())
 
2076
 
 
2077
    def _has_id(self, file_id, fallback_check):
 
2078
        if file_id in self._transform._r_new_id:
 
2079
            return True
 
2080
        elif file_id in set([self._transform.tree_file_id(trans_id) for
 
2081
            trans_id in self._transform._removed_id]):
 
2082
            return False
 
2083
        else:
 
2084
            return fallback_check(file_id)
 
2085
 
 
2086
    def has_id(self, file_id):
 
2087
        return self._has_id(file_id, self._transform._tree.has_id)
 
2088
 
 
2089
    def has_or_had_id(self, file_id):
 
2090
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
2121
2091
 
2122
2092
    def _path2trans_id(self, path):
2123
2093
        # We must not use None here, because that is a valid value to store.
2148
2118
            path = osutils.pathjoin(*path)
2149
2119
        return self._transform.final_file_id(self._path2trans_id(path))
2150
2120
 
2151
 
    def id2path(self, file_id, recurse='down'):
 
2121
    def id2path(self, file_id):
2152
2122
        trans_id = self._transform.trans_id_file_id(file_id)
2153
2123
        try:
2154
2124
            return self._final_paths._determine_path(trans_id)
2161
2131
            return children
2162
2132
        children = set(self._transform.iter_tree_children(trans_id))
2163
2133
        # children in the _new_parent set are provided by _by_parent.
2164
 
        children.difference_update(self._transform._new_parent)
 
2134
        children.difference_update(self._transform._new_parent.keys())
2165
2135
        children.update(self._by_parent.get(trans_id, []))
2166
2136
        self._all_children_cache[trans_id] = children
2167
2137
        return children
2168
2138
 
 
2139
    def iter_children(self, file_id):
 
2140
        trans_id = self._transform.trans_id_file_id(file_id)
 
2141
        for child_trans_id in self._all_children(trans_id):
 
2142
            yield self._transform.final_file_id(child_trans_id)
 
2143
 
2169
2144
    def extras(self):
2170
2145
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
2171
2146
                              in self._transform._tree.extras())
2175
2150
            if self._transform.final_file_id(trans_id) is None:
2176
2151
                yield self._final_paths._determine_path(trans_id)
2177
2152
 
2178
 
    def _make_inv_entries(self, ordered_entries, specific_files=None):
 
2153
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
2154
        yield_parents=False):
2179
2155
        for trans_id, parent_file_id in ordered_entries:
2180
2156
            file_id = self._transform.final_file_id(trans_id)
2181
2157
            if file_id is None:
2182
2158
                continue
2183
 
            if (specific_files is not None
2184
 
                    and self._final_paths.get_path(trans_id) not in specific_files):
 
2159
            if (specific_file_ids is not None
 
2160
                and file_id not in specific_file_ids):
2185
2161
                continue
2186
2162
            kind = self._transform.final_kind(trans_id)
2187
2163
            if kind is None:
2188
 
                kind = self._transform._tree.stored_kind(
2189
 
                    self._transform._tree.id2path(file_id))
 
2164
                kind = self._transform._tree.stored_kind(file_id)
2190
2165
            new_entry = inventory.make_entry(
2191
2166
                kind,
2192
2167
                self._transform.final_name(trans_id),
2207
2182
                ordered_ids.append((trans_id, parent_file_id))
2208
2183
        return ordered_ids
2209
2184
 
2210
 
    def iter_child_entries(self, path):
2211
 
        trans_id = self._path2trans_id(path)
2212
 
        if trans_id is None:
2213
 
            raise errors.NoSuchFile(path)
 
2185
    def iter_child_entries(self, file_id, path=None):
 
2186
        self.id2path(file_id)
 
2187
        trans_id = self._transform.trans_id_file_id(file_id)
2214
2188
        todo = [(child_trans_id, trans_id) for child_trans_id in
2215
2189
                self._all_children(trans_id)]
2216
2190
        for entry, trans_id in self._make_inv_entries(todo):
2217
2191
            yield entry
2218
2192
 
2219
 
    def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
2220
 
        if recurse_nested:
2221
 
            raise NotImplementedError(
2222
 
                'follow tree references not yet supported')
2223
 
 
 
2193
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2224
2194
        # This may not be a maximally efficient implementation, but it is
2225
2195
        # reasonably straightforward.  An implementation that grafts the
2226
2196
        # TreeTransform changes onto the tree's iter_entries_by_dir results
2228
2198
        # position.
2229
2199
        ordered_ids = self._list_files_by_dir()
2230
2200
        for entry, trans_id in self._make_inv_entries(ordered_ids,
2231
 
                                                      specific_files):
2232
 
            yield self._final_paths.get_path(trans_id), entry
 
2201
            specific_file_ids, yield_parents=yield_parents):
 
2202
            yield unicode(self._final_paths.get_path(trans_id)), entry
2233
2203
 
2234
2204
    def _iter_entries_for_dir(self, dir_path):
2235
2205
        """Return path, entry for items in a directory without recursing down."""
 
2206
        dir_file_id = self.path2id(dir_path)
2236
2207
        ordered_ids = []
2237
 
        dir_trans_id = self._path2trans_id(dir_path)
2238
 
        dir_id = self._transform.final_file_id(dir_trans_id)
2239
 
        for child_trans_id in self._all_children(dir_trans_id):
2240
 
            ordered_ids.append((child_trans_id, dir_id))
2241
 
        path_entries = []
 
2208
        for file_id in self.iter_children(dir_file_id):
 
2209
            trans_id = self._transform.trans_id_file_id(file_id)
 
2210
            ordered_ids.append((trans_id, file_id))
2242
2211
        for entry, trans_id in self._make_inv_entries(ordered_ids):
2243
 
            path_entries.append((self._final_paths.get_path(trans_id), entry))
2244
 
        path_entries.sort()
2245
 
        return path_entries
 
2212
            yield unicode(self._final_paths.get_path(trans_id)), entry
2246
2213
 
2247
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
2248
 
                   recurse_nested=False):
 
2214
    def list_files(self, include_root=False, from_dir=None, recursive=True):
2249
2215
        """See WorkingTree.list_files."""
2250
 
        if recurse_nested:
2251
 
            raise NotImplementedError(
2252
 
                'follow tree references not yet supported')
2253
 
 
2254
2216
        # XXX This should behave like WorkingTree.list_files, but is really
2255
2217
        # more like RevisionTree.list_files.
2256
 
        if from_dir == '.':
2257
 
            from_dir = None
2258
2218
        if recursive:
2259
2219
            prefix = None
2260
2220
            if from_dir:
2267
2227
                    if not path.startswith(prefix):
2268
2228
                        continue
2269
2229
                    path = path[len(prefix):]
2270
 
                yield path, 'V', entry.kind, entry
 
2230
                yield path, 'V', entry.kind, entry.file_id, entry
2271
2231
        else:
2272
2232
            if from_dir is None and include_root is True:
2273
 
                root_entry = inventory.make_entry(
2274
 
                    'directory', '', ROOT_PARENT, self.path2id(''))
2275
 
                yield '', 'V', 'directory', root_entry
 
2233
                root_entry = inventory.make_entry('directory', '',
 
2234
                    ROOT_PARENT, self.get_root_id())
 
2235
                yield '', 'V', 'directory', root_entry.file_id, root_entry
2276
2236
            entries = self._iter_entries_for_dir(from_dir or '')
2277
2237
            for path, entry in entries:
2278
 
                yield path, 'V', entry.kind, entry
 
2238
                yield path, 'V', entry.kind, entry.file_id, entry
2279
2239
 
2280
 
    def kind(self, path):
2281
 
        trans_id = self._path2trans_id(path)
2282
 
        if trans_id is None:
2283
 
            raise errors.NoSuchFile(path)
 
2240
    def kind(self, file_id):
 
2241
        trans_id = self._transform.trans_id_file_id(file_id)
2284
2242
        return self._transform.final_kind(trans_id)
2285
2243
 
2286
 
    def stored_kind(self, path):
2287
 
        trans_id = self._path2trans_id(path)
2288
 
        if trans_id is None:
2289
 
            raise errors.NoSuchFile(path)
 
2244
    def stored_kind(self, file_id):
 
2245
        trans_id = self._transform.trans_id_file_id(file_id)
2290
2246
        try:
2291
2247
            return self._transform._new_contents[trans_id]
2292
2248
        except KeyError:
2293
 
            return self._transform._tree.stored_kind(path)
 
2249
            return self._transform._tree.stored_kind(file_id)
2294
2250
 
2295
 
    def get_file_mtime(self, path):
 
2251
    def get_file_mtime(self, file_id, path=None):
2296
2252
        """See Tree.get_file_mtime"""
2297
 
        file_id = self.path2id(path)
2298
 
        if file_id is None:
2299
 
            raise errors.NoSuchFile(path)
2300
2253
        if not self._content_change(file_id):
2301
 
            return self._transform._tree.get_file_mtime(
2302
 
                self._transform._tree.id2path(file_id))
2303
 
        trans_id = self._path2trans_id(path)
2304
 
        return self._stat_limbo_file(trans_id).st_mtime
2305
 
 
2306
 
    def get_file_size(self, path):
 
2254
            return self._transform._tree.get_file_mtime(file_id)
 
2255
        return self._stat_limbo_file(file_id).st_mtime
 
2256
 
 
2257
    def _file_size(self, entry, stat_value):
 
2258
        return self.get_file_size(entry.file_id)
 
2259
 
 
2260
    def get_file_size(self, file_id):
2307
2261
        """See Tree.get_file_size"""
2308
 
        trans_id = self._path2trans_id(path)
2309
 
        if trans_id is None:
2310
 
            raise errors.NoSuchFile(path)
 
2262
        trans_id = self._transform.trans_id_file_id(file_id)
2311
2263
        kind = self._transform.final_kind(trans_id)
2312
2264
        if kind != 'file':
2313
2265
            return None
2314
2266
        if trans_id in self._transform._new_contents:
2315
 
            return self._stat_limbo_file(trans_id).st_size
2316
 
        if self.kind(path) == 'file':
2317
 
            return self._transform._tree.get_file_size(path)
 
2267
            return self._stat_limbo_file(trans_id=trans_id).st_size
 
2268
        if self.kind(file_id) == 'file':
 
2269
            return self._transform._tree.get_file_size(file_id)
2318
2270
        else:
2319
2271
            return None
2320
2272
 
2321
 
    def get_file_verifier(self, path, stat_value=None):
2322
 
        trans_id = self._path2trans_id(path)
2323
 
        if trans_id is None:
2324
 
            raise errors.NoSuchFile(path)
 
2273
    def get_file_verifier(self, file_id, path=None, stat_value=None):
 
2274
        trans_id = self._transform.trans_id_file_id(file_id)
2325
2275
        kind = self._transform._new_contents.get(trans_id)
2326
2276
        if kind is None:
2327
 
            return self._transform._tree.get_file_verifier(path)
 
2277
            return self._transform._tree.get_file_verifier(file_id)
2328
2278
        if kind == 'file':
2329
 
            with self.get_file(path) as fileobj:
 
2279
            fileobj = self.get_file(file_id)
 
2280
            try:
2330
2281
                return ("SHA1", sha_file(fileobj))
 
2282
            finally:
 
2283
                fileobj.close()
2331
2284
 
2332
 
    def get_file_sha1(self, path, stat_value=None):
2333
 
        trans_id = self._path2trans_id(path)
2334
 
        if trans_id is None:
2335
 
            raise errors.NoSuchFile(path)
 
2285
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
2286
        trans_id = self._transform.trans_id_file_id(file_id)
2336
2287
        kind = self._transform._new_contents.get(trans_id)
2337
2288
        if kind is None:
2338
 
            return self._transform._tree.get_file_sha1(path)
 
2289
            return self._transform._tree.get_file_sha1(file_id)
2339
2290
        if kind == 'file':
2340
 
            with self.get_file(path) as fileobj:
 
2291
            fileobj = self.get_file(file_id)
 
2292
            try:
2341
2293
                return sha_file(fileobj)
2342
 
 
2343
 
    def get_reference_revision(self, path):
2344
 
        trans_id = self._path2trans_id(path)
2345
 
        if trans_id is None:
2346
 
            raise errors.NoSuchFile(path)
2347
 
        reference_revision = self._transform._new_reference_revision.get(trans_id)
2348
 
        if reference_revision is None:
2349
 
            return self._transform._tree.get_reference_revision(path)
2350
 
        return reference_revision
2351
 
 
2352
 
    def is_executable(self, path):
2353
 
        trans_id = self._path2trans_id(path)
2354
 
        if trans_id is None:
 
2294
            finally:
 
2295
                fileobj.close()
 
2296
 
 
2297
    def is_executable(self, file_id, path=None):
 
2298
        if file_id is None:
2355
2299
            return False
 
2300
        trans_id = self._transform.trans_id_file_id(file_id)
2356
2301
        try:
2357
2302
            return self._transform._new_executability[trans_id]
2358
2303
        except KeyError:
2359
2304
            try:
2360
 
                return self._transform._tree.is_executable(path)
2361
 
            except OSError as e:
 
2305
                return self._transform._tree.is_executable(file_id, path)
 
2306
            except OSError, e:
2362
2307
                if e.errno == errno.ENOENT:
2363
2308
                    return False
2364
2309
                raise
2365
 
            except errors.NoSuchFile:
 
2310
            except errors.NoSuchId:
2366
2311
                return False
2367
2312
 
2368
2313
    def has_filename(self, path):
2400
2345
                size = None
2401
2346
                executable = None
2402
2347
            if kind == 'symlink':
2403
 
                link_or_sha1 = os.readlink(limbo_name)
2404
 
                if not isinstance(link_or_sha1, str):
2405
 
                    link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
 
2348
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2406
2349
        executable = tt._new_executability.get(trans_id, executable)
2407
2350
        return kind, size, executable, link_or_sha1
2408
2351
 
2409
2352
    def iter_changes(self, from_tree, include_unchanged=False,
2410
 
                     specific_files=None, pb=None, extra_trees=None,
2411
 
                     require_versioned=True, want_unversioned=False):
 
2353
                      specific_files=None, pb=None, extra_trees=None,
 
2354
                      require_versioned=True, want_unversioned=False):
2412
2355
        """See InterTree.iter_changes.
2413
2356
 
2414
2357
        This has a fast path that is only used when the from_tree matches
2415
2358
        the transform tree, and no fancy options are supplied.
2416
2359
        """
2417
 
        if (from_tree is not self._transform._tree or include_unchanged
2418
 
                or specific_files or want_unversioned):
 
2360
        if (from_tree is not self._transform._tree or include_unchanged or
 
2361
            specific_files or want_unversioned):
2419
2362
            return tree.InterTree(from_tree, self).iter_changes(
2420
2363
                include_unchanged=include_unchanged,
2421
2364
                specific_files=specific_files,
2427
2370
            raise ValueError('want_unversioned is not supported')
2428
2371
        return self._transform.iter_changes()
2429
2372
 
2430
 
    def get_file(self, path):
 
2373
    def get_file(self, file_id, path=None):
2431
2374
        """See Tree.get_file"""
2432
 
        file_id = self.path2id(path)
2433
2375
        if not self._content_change(file_id):
2434
 
            return self._transform._tree.get_file(path)
2435
 
        trans_id = self._path2trans_id(path)
 
2376
            return self._transform._tree.get_file(file_id, path)
 
2377
        trans_id = self._transform.trans_id_file_id(file_id)
2436
2378
        name = self._transform._limbo_name(trans_id)
2437
2379
        return open(name, 'rb')
2438
2380
 
2439
 
    def get_file_with_stat(self, path):
2440
 
        return self.get_file(path), None
 
2381
    def get_file_with_stat(self, file_id, path=None):
 
2382
        return self.get_file(file_id, path), None
2441
2383
 
2442
 
    def annotate_iter(self, path,
 
2384
    def annotate_iter(self, file_id,
2443
2385
                      default_revision=_mod_revision.CURRENT_REVISION):
2444
 
        file_id = self.path2id(path)
2445
2386
        changes = self._iter_changes_cache.get(file_id)
2446
2387
        if changes is None:
2447
2388
            get_old = True
2448
2389
        else:
2449
 
            changed_content, versioned, kind = (
2450
 
                changes.changed_content, changes.versioned, changes.kind)
 
2390
            changed_content, versioned, kind = (changes[2], changes[3],
 
2391
                                                changes[6])
2451
2392
            if kind[1] is None:
2452
2393
                return None
2453
2394
            get_old = (kind[0] == 'file' and versioned[0])
2454
2395
        if get_old:
2455
 
            old_annotation = self._transform._tree.annotate_iter(
2456
 
                path, default_revision=default_revision)
 
2396
            old_annotation = self._transform._tree.annotate_iter(file_id,
 
2397
                default_revision=default_revision)
2457
2398
        else:
2458
2399
            old_annotation = []
2459
2400
        if changes is None:
2468
2409
        #       It would be nice to be able to use the new Annotator based
2469
2410
        #       approach, as well.
2470
2411
        return annotate.reannotate([old_annotation],
2471
 
                                   self.get_file(path).readlines(),
 
2412
                                   self.get_file(file_id).readlines(),
2472
2413
                                   default_revision)
2473
2414
 
2474
 
    def get_symlink_target(self, path):
 
2415
    def get_symlink_target(self, file_id, path=None):
2475
2416
        """See Tree.get_symlink_target"""
2476
 
        file_id = self.path2id(path)
2477
2417
        if not self._content_change(file_id):
2478
 
            return self._transform._tree.get_symlink_target(path)
2479
 
        trans_id = self._path2trans_id(path)
 
2418
            return self._transform._tree.get_symlink_target(file_id)
 
2419
        trans_id = self._transform.trans_id_file_id(file_id)
2480
2420
        name = self._transform._limbo_name(trans_id)
2481
2421
        return osutils.readlink(name)
2482
2422
 
2493
2433
                path_from_root = self._final_paths.get_path(child_id)
2494
2434
                basename = self._transform.final_name(child_id)
2495
2435
                file_id = self._transform.final_file_id(child_id)
2496
 
                kind = self._transform.final_kind(child_id)
 
2436
                kind  = self._transform.final_kind(child_id)
2497
2437
                if kind is not None:
2498
2438
                    versioned_kind = kind
2499
2439
                else:
2500
2440
                    kind = 'unknown'
2501
 
                    versioned_kind = self._transform._tree.stored_kind(
2502
 
                        self._transform._tree.id2path(file_id))
 
2441
                    versioned_kind = self._transform._tree.stored_kind(file_id)
2503
2442
                if versioned_kind == 'directory':
2504
2443
                    subdirs.append(child_id)
2505
2444
                children.append((path_from_root, basename, kind, None,
2534
2473
    The underlying tree must not be manipulated between calls, or else
2535
2474
    the results will likely be incorrect.
2536
2475
    """
2537
 
 
2538
2476
    def __init__(self, transform):
2539
2477
        object.__init__(self)
2540
2478
        self._known_paths = {}
2542
2480
 
2543
2481
    def _determine_path(self, trans_id):
2544
2482
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2545
 
            return u""
 
2483
            return ""
2546
2484
        name = self.transform.final_name(trans_id)
2547
2485
        parent_id = self.transform.final_parent(trans_id)
2548
2486
        if parent_id == self.transform.root:
2560
2498
        return [(self.get_path(t), t) for t in trans_ids]
2561
2499
 
2562
2500
 
 
2501
 
 
2502
def topology_sorted_ids(tree):
 
2503
    """Determine the topological order of the ids in a tree"""
 
2504
    file_ids = list(tree)
 
2505
    file_ids.sort(key=tree.id2path)
 
2506
    return file_ids
 
2507
 
 
2508
 
2563
2509
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
2564
2510
               delta_from_tree=False):
2565
2511
    """Create working tree for a branch, using a TreeTransform.
2586
2532
    :param delta_from_tree: If true, build_tree may use the input Tree to
2587
2533
        generate the inventory delta.
2588
2534
    """
2589
 
    with contextlib.ExitStack() as exit_stack:
2590
 
        exit_stack.enter_context(wt.lock_tree_write())
2591
 
        exit_stack.enter_context(tree.lock_read())
2592
 
        if accelerator_tree is not None:
2593
 
            exit_stack.enter_context(accelerator_tree.lock_read())
2594
 
        return _build_tree(tree, wt, accelerator_tree, hardlink,
2595
 
                           delta_from_tree)
 
2535
    wt.lock_tree_write()
 
2536
    try:
 
2537
        tree.lock_read()
 
2538
        try:
 
2539
            if accelerator_tree is not None:
 
2540
                accelerator_tree.lock_read()
 
2541
            try:
 
2542
                return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2543
                                   delta_from_tree)
 
2544
            finally:
 
2545
                if accelerator_tree is not None:
 
2546
                    accelerator_tree.unlock()
 
2547
        finally:
 
2548
            tree.unlock()
 
2549
    finally:
 
2550
        wt.unlock()
2596
2551
 
2597
2552
 
2598
2553
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2599
2554
    """See build_tree."""
2600
 
    for num, _unused in enumerate(wt.all_versioned_paths()):
 
2555
    for num, _unused in enumerate(wt.all_file_ids()):
2601
2556
        if num > 0:  # more than just a root
2602
2557
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2603
2558
    file_trans_id = {}
2604
2559
    top_pb = ui.ui_factory.nested_progress_bar()
2605
2560
    pp = ProgressPhase("Build phase", 2, top_pb)
2606
 
    if tree.path2id('') is not None:
 
2561
    if tree.get_root_id() is not None:
2607
2562
        # This is kind of a hack: we should be altering the root
2608
2563
        # as part of the regular tree shape diff logic.
2609
2564
        # The conditional test here is to avoid doing an
2611
2566
        # is set within the tree, nor setting the root and thus
2612
2567
        # marking the tree as dirty, because we use two different
2613
2568
        # idioms here: tree interfaces and inventory interfaces.
2614
 
        if wt.path2id('') != tree.path2id(''):
2615
 
            wt.set_root_id(tree.path2id(''))
 
2569
        if wt.get_root_id() != tree.get_root_id():
 
2570
            wt.set_root_id(tree.get_root_id())
2616
2571
            wt.flush()
2617
 
    tt = wt.get_transform()
 
2572
    tt = TreeTransform(wt)
2618
2573
    divert = set()
2619
2574
    try:
2620
2575
        pp.next_phase()
2621
 
        file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
2622
 
        with ui.ui_factory.nested_progress_bar() as pb:
 
2576
        file_trans_id[wt.get_root_id()] = \
 
2577
            tt.trans_id_tree_file_id(wt.get_root_id())
 
2578
        pb = ui.ui_factory.nested_progress_bar()
 
2579
        try:
2623
2580
            deferred_contents = []
2624
2581
            num = 0
2625
 
            total = len(tree.all_versioned_paths())
 
2582
            total = len(tree.all_file_ids())
2626
2583
            if delta_from_tree:
2627
2584
                precomputed_delta = []
2628
2585
            else:
2637
2594
                for dir, files in wt.walkdirs():
2638
2595
                    existing_files.update(f[0] for f in files)
2639
2596
            for num, (tree_path, entry) in \
2640
 
                    enumerate(tree.iter_entries_by_dir()):
2641
 
                pb.update(gettext("Building tree"), num
2642
 
                          - len(deferred_contents), total)
 
2597
                enumerate(tree.iter_entries_by_dir()):
 
2598
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2643
2599
                if entry.parent_id is None:
2644
2600
                    continue
2645
2601
                reparent = False
2656
2612
                            pass
2657
2613
                        else:
2658
2614
                            divert.add(file_id)
2659
 
                    if (file_id not in divert
2660
 
                        and _content_match(
2661
 
                            tree, entry, tree_path, kind, target_path)):
 
2615
                    if (file_id not in divert and
 
2616
                        _content_match(tree, entry, file_id, kind,
 
2617
                        target_path)):
2662
2618
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2663
2619
                        if kind == 'directory':
2664
2620
                            reparent = True
2669
2625
                    trans_id = tt.create_path(entry.name, parent_id)
2670
2626
                    file_trans_id[file_id] = trans_id
2671
2627
                    tt.version_file(file_id, trans_id)
2672
 
                    executable = tree.is_executable(tree_path)
 
2628
                    executable = tree.is_executable(file_id, tree_path)
2673
2629
                    if executable:
2674
2630
                        tt.set_executability(executable, trans_id)
2675
 
                    trans_data = (trans_id, file_id,
2676
 
                                  tree_path, entry.text_sha1)
2677
 
                    deferred_contents.append((tree_path, trans_data))
 
2631
                    trans_data = (trans_id, tree_path, entry.text_sha1)
 
2632
                    deferred_contents.append((file_id, trans_data))
2678
2633
                else:
2679
 
                    file_trans_id[file_id] = new_by_entry(
2680
 
                        tree_path, tt, entry, parent_id, tree)
 
2634
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
2635
                                                          tree)
2681
2636
                if reparent:
2682
2637
                    new_trans_id = file_trans_id[file_id]
2683
2638
                    old_parent = tt.trans_id_tree_path(tree_path)
2685
2640
            offset = num + 1 - len(deferred_contents)
2686
2641
            _create_files(tt, tree, deferred_contents, pb, offset,
2687
2642
                          accelerator_tree, hardlink)
 
2643
        finally:
 
2644
            pb.finished()
2688
2645
        pp.next_phase()
2689
2646
        divert_trans = set(file_trans_id[f] for f in divert)
2690
 
 
2691
 
        def resolver(t, c):
2692
 
            return resolve_checkout(t, c, divert_trans)
 
2647
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2693
2648
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2694
2649
        if len(raw_conflicts) > 0:
2695
2650
            precomputed_delta = None
2696
2651
        conflicts = cook_conflicts(raw_conflicts, tt)
2697
2652
        for conflict in conflicts:
2698
 
            trace.warning(str(conflict))
 
2653
            trace.warning(unicode(conflict))
2699
2654
        try:
2700
2655
            wt.add_conflicts(conflicts)
2701
2656
        except errors.UnsupportedOperation:
2716
2671
        new_desired_files = desired_files
2717
2672
    else:
2718
2673
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2719
 
        unchanged = [
2720
 
            change.path for change in iter
2721
 
            if not (change.changed_content or change.executable[0] != change.executable[1])]
 
2674
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2675
                     in iter if not (c or e[0] != e[1])]
2722
2676
        if accelerator_tree.supports_content_filtering():
2723
 
            unchanged = [(tp, ap) for (tp, ap) in unchanged
2724
 
                         if not next(accelerator_tree.iter_search_rules([ap]))]
 
2677
            unchanged = [(f, p) for (f, p) in unchanged
 
2678
                         if not accelerator_tree.iter_search_rules([p]).next()]
2725
2679
        unchanged = dict(unchanged)
2726
2680
        new_desired_files = []
2727
2681
        count = 0
2728
 
        for unused_tree_path, (trans_id, file_id, tree_path, text_sha1) in desired_files:
2729
 
            accelerator_path = unchanged.get(tree_path)
 
2682
        for file_id, (trans_id, tree_path, text_sha1) in desired_files:
 
2683
            accelerator_path = unchanged.get(file_id)
2730
2684
            if accelerator_path is None:
2731
 
                new_desired_files.append((tree_path,
2732
 
                                          (trans_id, file_id, tree_path, text_sha1)))
 
2685
                new_desired_files.append((file_id,
 
2686
                    (trans_id, tree_path, text_sha1)))
2733
2687
                continue
2734
2688
            pb.update(gettext('Adding file contents'), count + offset, total)
2735
2689
            if hardlink:
2736
2690
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2737
2691
                                   trans_id)
2738
2692
            else:
2739
 
                with accelerator_tree.get_file(accelerator_path) as f:
2740
 
                    chunks = osutils.file_iterator(f)
2741
 
                    if wt.supports_content_filtering():
2742
 
                        filters = wt._content_filter_stack(tree_path)
2743
 
                        chunks = filtered_output_bytes(chunks, filters,
2744
 
                                                       ContentFilterContext(tree_path, tree))
2745
 
                    tt.create_file(chunks, trans_id, sha1=text_sha1)
 
2693
                contents = accelerator_tree.get_file(file_id, accelerator_path)
 
2694
                if wt.supports_content_filtering():
 
2695
                    filters = wt._content_filter_stack(tree_path)
 
2696
                    contents = filtered_output_bytes(contents, filters,
 
2697
                        ContentFilterContext(tree_path, tree))
 
2698
                try:
 
2699
                    tt.create_file(contents, trans_id, sha1=text_sha1)
 
2700
                finally:
 
2701
                    try:
 
2702
                        contents.close()
 
2703
                    except AttributeError:
 
2704
                        # after filtering, contents may no longer be file-like
 
2705
                        pass
2746
2706
            count += 1
2747
2707
        offset += count
2748
 
    for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
 
2708
    for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2749
2709
            tree.iter_files_bytes(new_desired_files)):
2750
2710
        if wt.supports_content_filtering():
2751
2711
            filters = wt._content_filter_stack(tree_path)
2752
2712
            contents = filtered_output_bytes(contents, filters,
2753
 
                                             ContentFilterContext(tree_path, tree))
 
2713
                ContentFilterContext(tree_path, tree))
2754
2714
        tt.create_file(contents, trans_id, sha1=text_sha1)
2755
2715
        pb.update(gettext('Adding file contents'), count + offset, total)
2756
2716
 
2767
2727
    return by_parent[old_parent]
2768
2728
 
2769
2729
 
2770
 
def _content_match(tree, entry, tree_path, kind, target_path):
 
2730
def _content_match(tree, entry, file_id, kind, target_path):
2771
2731
    if entry.kind != kind:
2772
2732
        return False
2773
2733
    if entry.kind == "directory":
2774
2734
        return True
2775
2735
    if entry.kind == "file":
2776
 
        with open(target_path, 'rb') as f1, \
2777
 
                tree.get_file(tree_path) as f2:
2778
 
            if osutils.compare_files(f1, f2):
 
2736
        f = file(target_path, 'rb')
 
2737
        try:
 
2738
            if tree.get_file_text(file_id) == f.read():
2779
2739
                return True
 
2740
        finally:
 
2741
            f.close()
2780
2742
    elif entry.kind == "symlink":
2781
 
        if tree.get_symlink_target(tree_path) == os.readlink(target_path):
 
2743
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
2782
2744
            return True
2783
2745
    return False
2784
2746
 
2801
2763
        # resolved
2802
2764
        final_parent = tt.final_parent(old_file)
2803
2765
        if new_file in divert:
2804
 
            new_name = tt.final_name(old_file) + '.diverted'
 
2766
            new_name = tt.final_name(old_file)+'.diverted'
2805
2767
            tt.adjust_path(new_name, final_parent, new_file)
2806
2768
            new_conflicts.add((c_type, 'Diverted to',
2807
2769
                               new_file, old_file))
2808
2770
        else:
2809
 
            new_name = tt.final_name(old_file) + '.moved'
 
2771
            new_name = tt.final_name(old_file)+'.moved'
2810
2772
            tt.adjust_path(new_name, final_parent, old_file)
2811
2773
            new_conflicts.add((c_type, 'Moved existing file to',
2812
2774
                               old_file, new_file))
2813
2775
    return new_conflicts
2814
2776
 
2815
2777
 
2816
 
def new_by_entry(path, tt, entry, parent_id, tree):
 
2778
def new_by_entry(tt, entry, parent_id, tree):
2817
2779
    """Create a new file according to its inventory entry"""
2818
2780
    name = entry.name
2819
2781
    kind = entry.kind
2820
2782
    if kind == 'file':
2821
 
        with tree.get_file(path) as f:
2822
 
            executable = tree.is_executable(path)
2823
 
            return tt.new_file(
2824
 
                name, parent_id, osutils.file_iterator(f), entry.file_id,
2825
 
                executable)
 
2783
        contents = tree.get_file(entry.file_id).readlines()
 
2784
        executable = tree.is_executable(entry.file_id)
 
2785
        return tt.new_file(name, parent_id, contents, entry.file_id,
 
2786
                           executable)
2826
2787
    elif kind in ('directory', 'tree-reference'):
2827
2788
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
2828
2789
        if kind == 'tree-reference':
2829
2790
            tt.set_tree_reference(entry.reference_revision, trans_id)
2830
2791
        return trans_id
2831
2792
    elif kind == 'symlink':
2832
 
        target = tree.get_symlink_target(path)
 
2793
        target = tree.get_symlink_target(entry.file_id)
2833
2794
        return tt.new_symlink(name, parent_id, target, entry.file_id)
2834
2795
    else:
2835
2796
        raise errors.BadFileKindError(name, kind)
2836
2797
 
2837
2798
 
2838
 
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2839
 
                     filter_tree_path=None):
 
2799
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2800
    filter_tree_path=None):
2840
2801
    """Create new file contents according to tree contents.
2841
 
 
 
2802
    
2842
2803
    :param filter_tree_path: the tree path to use to lookup
2843
2804
      content filters to apply to the bytes output in the working tree.
2844
2805
      This only applies if the working tree supports content filtering.
2845
2806
    """
2846
 
    kind = tree.kind(path)
 
2807
    kind = tree.kind(file_id)
2847
2808
    if kind == 'directory':
2848
2809
        tt.create_directory(trans_id)
2849
2810
    elif kind == "file":
2850
 
        if chunks is None:
2851
 
            f = tree.get_file(path)
2852
 
            chunks = osutils.file_iterator(f)
2853
 
        else:
2854
 
            f = None
2855
 
        try:
2856
 
            wt = tt._tree
2857
 
            if wt.supports_content_filtering() and filter_tree_path is not None:
2858
 
                filters = wt._content_filter_stack(filter_tree_path)
2859
 
                chunks = filtered_output_bytes(
2860
 
                    chunks, filters,
2861
 
                    ContentFilterContext(filter_tree_path, tree))
2862
 
            tt.create_file(chunks, trans_id)
2863
 
        finally:
2864
 
            if f is not None:
2865
 
                f.close()
 
2811
        if bytes is None:
 
2812
            tree_file = tree.get_file(file_id)
 
2813
            try:
 
2814
                bytes = tree_file.readlines()
 
2815
            finally:
 
2816
                tree_file.close()
 
2817
        wt = tt._tree
 
2818
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2819
            filters = wt._content_filter_stack(filter_tree_path)
 
2820
            bytes = filtered_output_bytes(bytes, filters,
 
2821
                ContentFilterContext(filter_tree_path, tree))
 
2822
        tt.create_file(bytes, trans_id)
2866
2823
    elif kind == "symlink":
2867
 
        tt.create_symlink(tree.get_symlink_target(path), trans_id)
 
2824
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2868
2825
    else:
2869
2826
        raise AssertionError('Unknown kind %r' % kind)
2870
2827
 
2878
2835
def revert(working_tree, target_tree, filenames, backups=False,
2879
2836
           pb=None, change_reporter=None):
2880
2837
    """Revert a working tree's contents to those of a target tree."""
 
2838
    target_tree.lock_read()
2881
2839
    pb = ui.ui_factory.nested_progress_bar()
 
2840
    tt = TreeTransform(working_tree, pb)
2882
2841
    try:
2883
 
        with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2884
 
            pp = ProgressPhase("Revert phase", 3, pb)
2885
 
            conflicts, merge_modified = _prepare_revert_transform(
2886
 
                working_tree, target_tree, tt, filenames, backups, pp)
2887
 
            if change_reporter:
2888
 
                change_reporter = delta._ChangeReporter(
2889
 
                    unversioned_filter=working_tree.is_ignored)
2890
 
                delta.report_changes(tt.iter_changes(), change_reporter)
2891
 
            for conflict in conflicts:
2892
 
                trace.warning(str(conflict))
2893
 
            pp.next_phase()
2894
 
            tt.apply()
2895
 
            if working_tree.supports_merge_modified():
2896
 
                working_tree.set_merge_modified(merge_modified)
 
2842
        pp = ProgressPhase("Revert phase", 3, pb)
 
2843
        conflicts, merge_modified = _prepare_revert_transform(
 
2844
            working_tree, target_tree, tt, filenames, backups, pp)
 
2845
        if change_reporter:
 
2846
            change_reporter = delta._ChangeReporter(
 
2847
                unversioned_filter=working_tree.is_ignored)
 
2848
            delta.report_changes(tt.iter_changes(), change_reporter)
 
2849
        for conflict in conflicts:
 
2850
            trace.warning(unicode(conflict))
 
2851
        pp.next_phase()
 
2852
        tt.apply()
 
2853
        working_tree.set_merge_modified(merge_modified)
2897
2854
    finally:
 
2855
        target_tree.unlock()
 
2856
        tt.finalize()
2898
2857
        pb.clear()
2899
2858
    return conflicts
2900
2859
 
2902
2861
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2903
2862
                              backups, pp, basis_tree=None,
2904
2863
                              merge_modified=None):
2905
 
    with ui.ui_factory.nested_progress_bar() as child_pb:
 
2864
    child_pb = ui.ui_factory.nested_progress_bar()
 
2865
    try:
2906
2866
        if merge_modified is None:
2907
2867
            merge_modified = working_tree.merge_modified()
2908
2868
        merge_modified = _alter_files(working_tree, target_tree, tt,
2909
2869
                                      child_pb, filenames, backups,
2910
2870
                                      merge_modified, basis_tree)
2911
 
    with ui.ui_factory.nested_progress_bar() as child_pb:
2912
 
        raw_conflicts = resolve_conflicts(
2913
 
            tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
 
2871
    finally:
 
2872
        child_pb.finished()
 
2873
    child_pb = ui.ui_factory.nested_progress_bar()
 
2874
    try:
 
2875
        raw_conflicts = resolve_conflicts(tt, child_pb,
 
2876
            lambda t, c: conflict_pass(t, c, target_tree))
 
2877
    finally:
 
2878
        child_pb.finished()
2914
2879
    conflicts = cook_conflicts(raw_conflicts, tt)
2915
2880
    return conflicts, merge_modified
2916
2881
 
2923
2888
    # than the target changes relative to the working tree. Because WT4 has an
2924
2889
    # optimizer to compare itself to a target, but no optimizer for the
2925
2890
    # reverse.
2926
 
    change_list = working_tree.iter_changes(
2927
 
        target_tree, specific_files=specific_files, pb=pb)
2928
 
    if not target_tree.is_versioned(u''):
 
2891
    change_list = working_tree.iter_changes(target_tree,
 
2892
        specific_files=specific_files, pb=pb)
 
2893
    if target_tree.get_root_id() is None:
2929
2894
        skip_root = True
2930
2895
    else:
2931
2896
        skip_root = False
2932
2897
    try:
2933
2898
        deferred_files = []
2934
 
        for id_num, change in enumerate(change_list):
2935
 
            file_id = change.file_id
2936
 
            target_path, wt_path = change.path
2937
 
            target_versioned, wt_versioned = change.versioned
2938
 
            target_parent, wt_parent = change.parent_id
2939
 
            target_name, wt_name = change.name
2940
 
            target_kind, wt_kind = change.kind
2941
 
            target_executable, wt_executable = change.executable
 
2899
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
2900
                kind, executable) in enumerate(change_list):
 
2901
            target_path, wt_path = path
 
2902
            target_versioned, wt_versioned = versioned
 
2903
            target_parent, wt_parent = parent
 
2904
            target_name, wt_name = name
 
2905
            target_kind, wt_kind = kind
 
2906
            target_executable, wt_executable = executable
2942
2907
            if skip_root and wt_parent is None:
2943
2908
                continue
2944
2909
            trans_id = tt.trans_id_file_id(file_id)
2945
2910
            mode_id = None
2946
 
            if change.changed_content:
 
2911
            if changed_content:
2947
2912
                keep_content = False
2948
2913
                if wt_kind == 'file' and (backups or target_kind is None):
2949
 
                    wt_sha1 = working_tree.get_file_sha1(wt_path)
2950
 
                    if merge_modified.get(wt_path) != wt_sha1:
 
2914
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
2915
                    if merge_modified.get(file_id) != wt_sha1:
2951
2916
                        # acquire the basis tree lazily to prevent the
2952
2917
                        # expense of accessing it when it's not needed ?
2953
2918
                        # (Guessing, RBC, 200702)
2954
2919
                        if basis_tree is None:
2955
2920
                            basis_tree = working_tree.basis_tree()
2956
2921
                            basis_tree.lock_read()
2957
 
                        basis_inter = InterTree.get(basis_tree, working_tree)
2958
 
                        basis_path = basis_inter.find_source_path(wt_path)
2959
 
                        if basis_path is None:
2960
 
                            if target_kind is None and not target_versioned:
2961
 
                                keep_content = True
2962
 
                        else:
2963
 
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2964
 
                                keep_content = True
 
2922
                        if basis_tree.has_id(file_id):
 
2923
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
2924
                                keep_content = True
 
2925
                        elif target_kind is None and not target_versioned:
 
2926
                            keep_content = True
2965
2927
                if wt_kind is not None:
2966
2928
                    if not keep_content:
2967
2929
                        tt.delete_contents(trans_id)
2981
2943
                if target_kind in ('directory', 'tree-reference'):
2982
2944
                    tt.create_directory(trans_id)
2983
2945
                    if target_kind == 'tree-reference':
2984
 
                        revision = target_tree.get_reference_revision(
2985
 
                            target_path)
 
2946
                        revision = target_tree.get_reference_revision(file_id,
 
2947
                                                                      target_path)
2986
2948
                        tt.set_tree_reference(revision, trans_id)
2987
2949
                elif target_kind == 'symlink':
2988
 
                    tt.create_symlink(target_tree.get_symlink_target(
2989
 
                        target_path), trans_id)
 
2950
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
2951
                                      trans_id)
2990
2952
                elif target_kind == 'file':
2991
 
                    deferred_files.append(
2992
 
                        (target_path, (trans_id, mode_id, file_id)))
 
2953
                    deferred_files.append((file_id, (trans_id, mode_id)))
2993
2954
                    if basis_tree is None:
2994
2955
                        basis_tree = working_tree.basis_tree()
2995
2956
                        basis_tree.lock_read()
2996
 
                    new_sha1 = target_tree.get_file_sha1(target_path)
2997
 
                    basis_inter = InterTree.get(basis_tree, target_tree)
2998
 
                    basis_path = basis_inter.find_source_path(target_path)
2999
 
                    if (basis_path is not None and
3000
 
                            new_sha1 == basis_tree.get_file_sha1(basis_path)):
3001
 
                        # If the new contents of the file match what is in basis,
3002
 
                        # then there is no need to store in merge_modified.
3003
 
                        if basis_path in merge_modified:
3004
 
                            del merge_modified[basis_path]
 
2957
                    new_sha1 = target_tree.get_file_sha1(file_id)
 
2958
                    if (basis_tree.has_id(file_id) and
 
2959
                        new_sha1 == basis_tree.get_file_sha1(file_id)):
 
2960
                        if file_id in merge_modified:
 
2961
                            del merge_modified[file_id]
3005
2962
                    else:
3006
 
                        merge_modified[target_path] = new_sha1
 
2963
                        merge_modified[file_id] = new_sha1
3007
2964
 
3008
2965
                    # preserve the execute bit when backing up
3009
2966
                    if keep_content and wt_executable == target_executable:
3014
2971
                tt.version_file(file_id, trans_id)
3015
2972
            if wt_versioned and not target_versioned:
3016
2973
                tt.unversion_file(trans_id)
3017
 
            if (target_name is not None
3018
 
                    and (wt_name != target_name or wt_parent != target_parent)):
 
2974
            if (target_name is not None and
 
2975
                (wt_name != target_name or wt_parent != target_parent)):
3019
2976
                if target_name == '' and target_parent is None:
3020
2977
                    parent_trans = ROOT_PARENT
3021
2978
                else:
3027
2984
            if wt_executable != target_executable and target_kind == "file":
3028
2985
                tt.set_executability(target_executable, trans_id)
3029
2986
        if working_tree.supports_content_filtering():
3030
 
            for (trans_id, mode_id, file_id), bytes in (
3031
 
                    target_tree.iter_files_bytes(deferred_files)):
 
2987
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2988
                target_tree.iter_files_bytes(deferred_files)):
 
2989
                file_id = deferred_files[index][0]
3032
2990
                # We're reverting a tree to the target tree so using the
3033
2991
                # target tree to find the file path seems the best choice
3034
2992
                # here IMO - Ian C 27/Oct/2009
3035
2993
                filter_tree_path = target_tree.id2path(file_id)
3036
2994
                filters = working_tree._content_filter_stack(filter_tree_path)
3037
 
                bytes = filtered_output_bytes(
3038
 
                    bytes, filters,
 
2995
                bytes = filtered_output_bytes(bytes, filters,
3039
2996
                    ContentFilterContext(filter_tree_path, working_tree))
3040
2997
                tt.create_file(bytes, trans_id, mode_id)
3041
2998
        else:
3042
 
            for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3043
 
                    deferred_files):
 
2999
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
3000
                deferred_files):
3044
3001
                tt.create_file(bytes, trans_id, mode_id)
3045
3002
        tt.fixup_new_roots()
3046
3003
    finally:
3054
3011
    if pass_func is None:
3055
3012
        pass_func = conflict_pass
3056
3013
    new_conflicts = set()
3057
 
    with ui.ui_factory.nested_progress_bar() as pb:
 
3014
    pb = ui.ui_factory.nested_progress_bar()
 
3015
    try:
3058
3016
        for n in range(10):
3059
 
            pb.update(gettext('Resolution pass'), n + 1, 10)
 
3017
            pb.update(gettext('Resolution pass'), n+1, 10)
3060
3018
            conflicts = tt.find_conflicts()
3061
3019
            if len(conflicts) == 0:
3062
3020
                return new_conflicts
3063
3021
            new_conflicts.update(pass_func(tt, conflicts))
3064
3022
        raise MalformedTransform(conflicts=conflicts)
 
3023
    finally:
 
3024
        pb.finished()
3065
3025
 
3066
3026
 
3067
3027
def conflict_pass(tt, conflicts, path_tree=None):
3131
3091
                        file_id = tt.final_file_id(trans_id)
3132
3092
                        if file_id is None:
3133
3093
                            file_id = tt.inactive_file_id(trans_id)
3134
 
                        _, entry = next(path_tree.iter_entries_by_dir(
3135
 
                            specific_files=[path_tree.id2path(file_id)]))
 
3094
                        _, entry = path_tree.iter_entries_by_dir(
 
3095
                            [file_id]).next()
3136
3096
                        # special-case the other tree root (move its
3137
3097
                        # children to current root)
3138
3098
                        if entry.parent_id is None:
3153
3113
        elif c_type == 'unversioned parent':
3154
3114
            file_id = tt.inactive_file_id(conflict[1])
3155
3115
            # special-case the other tree root (move its children instead)
3156
 
            if path_tree and path_tree.path2id('') == file_id:
3157
 
                # This is the root entry, skip it
3158
 
                continue
 
3116
            if path_tree and path_tree.has_id(file_id):
 
3117
                if path_tree.path2id('') == file_id:
 
3118
                    # This is the root entry, skip it
 
3119
                    continue
3159
3120
            tt.version_file(file_id, conflict[1])
3160
3121
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
3161
3122
        elif c_type == 'non-directory parent':
3164
3125
            parent_name = tt.final_name(parent_id)
3165
3126
            parent_file_id = tt.final_file_id(parent_id)
3166
3127
            new_parent_id = tt.new_directory(parent_name + '.new',
3167
 
                                             parent_parent, parent_file_id)
 
3128
                parent_parent, parent_file_id)
3168
3129
            _reparent_transform_children(tt, parent_id, new_parent_id)
3169
3130
            if parent_file_id is not None:
3170
3131
                tt.unversion_file(parent_id)
3212
3173
        """Rename a file from one path to another."""
3213
3174
        try:
3214
3175
            os.rename(from_, to)
3215
 
        except OSError as e:
 
3176
        except OSError, e:
3216
3177
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
3217
3178
                raise errors.FileExists(to, str(e))
3218
3179
            # normal OSError doesn't include filenames so it's hard to see where
3235
3196
        for from_, to in reversed(self.past_renames):
3236
3197
            try:
3237
3198
                os.rename(to, from_)
3238
 
            except OSError as e:
 
3199
            except OSError, e:
3239
3200
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
3240
3201
        # after rollback, don't reuse _FileMover
3241
 
        self.past_renames = None
3242
 
        self.pending_deletions = None
 
3202
        past_renames = None
 
3203
        pending_deletions = None
3243
3204
 
3244
3205
    def apply_deletions(self):
3245
3206
        """Apply all marked deletions"""
3246
3207
        for path in self.pending_deletions:
3247
3208
            delete_any(path)
3248
3209
        # after apply_deletions, don't reuse _FileMover
3249
 
        self.past_renames = None
3250
 
        self.pending_deletions = None
3251
 
 
3252
 
 
3253
 
def link_tree(target_tree, source_tree):
3254
 
    """Where possible, hard-link files in a tree to those in another tree.
3255
 
 
3256
 
    :param target_tree: Tree to change
3257
 
    :param source_tree: Tree to hard-link from
3258
 
    """
3259
 
    with target_tree.get_transform() as tt:
3260
 
        for change in target_tree.iter_changes(source_tree, include_unchanged=True):
3261
 
            if change.changed_content:
3262
 
                continue
3263
 
            if change.kind != ('file', 'file'):
3264
 
                continue
3265
 
            if change.executable[0] != change.executable[1]:
3266
 
                continue
3267
 
            trans_id = tt.trans_id_tree_path(change.path[1])
3268
 
            tt.delete_contents(trans_id)
3269
 
            tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
3270
 
        tt.apply()
 
3210
        past_renames = None
 
3211
        pending_deletions = None