/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Andrew Bennetts
  • Date: 2009-10-21 11:13:40 UTC
  • mto: This revision was merged to the branch mainline in revision 4762.
  • Revision ID: andrew.bennetts@canonical.com-20091021111340-w7x4d5yf83qwjncc
Add test that WSGI glue allows request handlers to access paths above that request's. backing transport, so long as it is within the WSGI app's backing transport.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import os
20
18
import errno
21
19
from stat import S_ISREG, S_IEXEC
22
 
import time
23
20
 
24
 
from . import (
25
 
    config as _mod_config,
26
 
    errors,
27
 
    lazy_import,
28
 
    registry,
29
 
    trace,
30
 
    tree,
31
 
    )
32
 
lazy_import.lazy_import(globals(), """
33
 
from breezy import (
 
21
from bzrlib.lazy_import import lazy_import
 
22
lazy_import(globals(), """
 
23
from bzrlib import (
34
24
    annotate,
35
25
    bencode,
36
 
    controldir,
37
 
    commit,
38
 
    conflicts,
 
26
    bzrdir,
39
27
    delta,
40
 
    lock,
 
28
    errors,
 
29
    inventory,
41
30
    multiparent,
42
31
    osutils,
43
32
    revision as _mod_revision,
44
 
    ui,
45
 
    urlutils,
46
 
    )
47
 
from breezy.bzr import (
48
 
    inventory,
49
 
    inventorytree,
50
 
    )
51
 
from breezy.i18n import gettext
 
33
    )
52
34
""")
53
 
from .errors import (DuplicateKey, MalformedTransform,
54
 
                     ReusingTransform, CantMoveRoot,
55
 
                     ImmortalLimbo, NoFinalPath,
56
 
                     UnableCreateSymlink)
57
 
from .filters import filtered_output_bytes, ContentFilterContext
58
 
from .mutabletree import MutableTree
59
 
from .osutils import (
 
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
36
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
37
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
 
38
                           UnableCreateSymlink)
 
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
 
40
from bzrlib.inventory import InventoryEntry
 
41
from bzrlib.osutils import (
60
42
    delete_any,
61
43
    file_kind,
62
44
    has_symlinks,
 
45
    lexists,
63
46
    pathjoin,
64
47
    sha_file,
65
48
    splitpath,
66
 
    )
67
 
from .progress import ProgressPhase
68
 
from .sixish import (
69
 
    text_type,
70
 
    viewitems,
71
 
    viewvalues,
72
 
    )
73
 
from .tree import (
74
 
    find_previous_path,
75
 
    )
 
49
    supports_executable,
 
50
)
 
51
from bzrlib.progress import DummyProgress, ProgressPhase
 
52
from bzrlib.symbol_versioning import (
 
53
        deprecated_function,
 
54
        deprecated_in,
 
55
        )
 
56
from bzrlib.trace import mutter, warning
 
57
from bzrlib import tree
 
58
import bzrlib.ui
 
59
import bzrlib.urlutils as urlutils
76
60
 
77
61
 
78
62
ROOT_PARENT = "root-parent"
79
63
 
 
64
 
80
65
def unique_add(map, key, value):
81
66
    if key in map:
82
67
        raise DuplicateKey(key=key)
83
68
    map[key] = value
84
69
 
85
70
 
86
 
 
87
71
class _TransformResults(object):
88
72
    def __init__(self, modified_paths, rename_count):
89
73
        object.__init__(self)
94
78
class TreeTransformBase(object):
95
79
    """The base class for TreeTransform and its kin."""
96
80
 
97
 
    def __init__(self, tree, pb=None, case_sensitive=True):
 
81
    def __init__(self, tree, pb=DummyProgress(),
 
82
                 case_sensitive=True):
98
83
        """Constructor.
99
84
 
100
85
        :param tree: The tree that will be transformed, but not necessarily
101
86
            the output tree.
102
 
        :param pb: ignored
 
87
        :param pb: A ProgressTask indicating how much progress is being made
103
88
        :param case_sensitive: If True, the target of the transform is
104
89
            case sensitive, not just case preserving.
105
90
        """
112
97
        self._new_parent = {}
113
98
        # mapping of trans_id with new contents -> new file_kind
114
99
        self._new_contents = {}
115
 
        # mapping of trans_id => (sha1 of content, stat_value)
116
 
        self._observed_sha1s = {}
117
100
        # Set of trans_ids whose contents will be removed
118
101
        self._removed_contents = set()
119
102
        # Mapping of trans_id -> new execute-bit value
133
116
        # Mapping trans_id -> path in old tree
134
117
        self._tree_id_paths = {}
135
118
        # The trans_id that will be used as the tree root
136
 
        if tree.is_versioned(''):
137
 
            self._new_root = self.trans_id_tree_path('')
 
119
        root_id = tree.get_root_id()
 
120
        if root_id is not None:
 
121
            self._new_root = self.trans_id_tree_file_id(root_id)
138
122
        else:
139
123
            self._new_root = None
140
 
        # Indicator of whether the transform has been applied
 
124
        # Indictor of whether the transform has been applied
141
125
        self._done = False
142
126
        # A progress bar
143
127
        self._pb = pb
146
130
        # A counter of how many files have been renamed
147
131
        self.rename_count = 0
148
132
 
149
 
    def __enter__(self):
150
 
        """Support Context Manager API."""
151
 
        return self
152
 
 
153
 
    def __exit__(self, exc_type, exc_val, exc_tb):
154
 
        """Support Context Manager API."""
155
 
        self.finalize()
156
 
 
157
133
    def finalize(self):
158
134
        """Release the working tree lock, if held.
159
135
 
162
138
        """
163
139
        if self._tree is None:
164
140
            return
165
 
        for hook in MutableTree.hooks['post_transform']:
166
 
            hook(self._tree, self)
167
141
        self._tree.unlock()
168
142
        self._tree = None
169
143
 
187
161
 
188
162
    def adjust_path(self, name, parent, trans_id):
189
163
        """Change the path that is assigned to a transaction id."""
190
 
        if parent is None:
191
 
            raise ValueError("Parent trans-id may not be None")
192
164
        if trans_id == self._new_root:
193
165
            raise CantMoveRoot
194
166
        self._new_name[trans_id] = name
195
167
        self._new_parent[trans_id] = parent
 
168
        if parent == ROOT_PARENT:
 
169
            if self._new_root is not None:
 
170
                raise ValueError("Cannot have multiple roots.")
 
171
            self._new_root = trans_id
196
172
 
197
173
    def adjust_root_path(self, name, parent):
198
174
        """Emulate moving the root by moving all children, instead.
218
194
        # the physical root needs a new transaction id
219
195
        self._tree_path_ids.pop("")
220
196
        self._tree_id_paths.pop(old_root)
221
 
        self._new_root = self.trans_id_tree_path('')
 
197
        self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
222
198
        if parent == old_root:
223
199
            parent = self._new_root
224
200
        self.adjust_path(name, parent, old_root)
226
202
        self.version_file(old_root_file_id, old_root)
227
203
        self.unversion_file(self._new_root)
228
204
 
229
 
    def fixup_new_roots(self):
230
 
        """Reinterpret requests to change the root directory
231
 
 
232
 
        Instead of creating a root directory, or moving an existing directory,
233
 
        all the attributes and children of the new root are applied to the
234
 
        existing root directory.
235
 
 
236
 
        This means that the old root trans-id becomes obsolete, so it is
237
 
        recommended only to invoke this after the root trans-id has become
238
 
        irrelevant.
239
 
 
 
205
    def trans_id_tree_file_id(self, inventory_id):
 
206
        """Determine the transaction id of a working tree file.
 
207
 
 
208
        This reflects only files that already exist, not ones that will be
 
209
        added by transactions.
240
210
        """
241
 
        new_roots = [k for k, v in viewitems(self._new_parent)
242
 
                     if v == ROOT_PARENT]
243
 
        if len(new_roots) < 1:
244
 
            return
245
 
        if len(new_roots) != 1:
246
 
            raise ValueError('A tree cannot have two roots!')
247
 
        if self._new_root is None:
248
 
            self._new_root = new_roots[0]
249
 
            return
250
 
        old_new_root = new_roots[0]
251
 
        # unversion the new root's directory.
252
 
        if self.final_kind(self._new_root) is None:
253
 
            file_id = self.final_file_id(old_new_root)
254
 
        else:
255
 
            file_id = self.final_file_id(self._new_root)
256
 
        if old_new_root in self._new_id:
257
 
            self.cancel_versioning(old_new_root)
258
 
        else:
259
 
            self.unversion_file(old_new_root)
260
 
        # if, at this stage, root still has an old file_id, zap it so we can
261
 
        # stick a new one in.
262
 
        if (self.tree_file_id(self._new_root) is not None and
263
 
            self._new_root not in self._removed_id):
264
 
            self.unversion_file(self._new_root)
265
 
        if file_id is not None:
266
 
            self.version_file(file_id, self._new_root)
267
 
 
268
 
        # Now move children of new root into old root directory.
269
 
        # Ensure all children are registered with the transaction, but don't
270
 
        # use directly-- some tree children have new parents
271
 
        list(self.iter_tree_children(old_new_root))
272
 
        # Move all children of new root into old root directory.
273
 
        for child in self.by_parent().get(old_new_root, []):
274
 
            self.adjust_path(self.final_name(child), self._new_root, child)
275
 
 
276
 
        # Ensure old_new_root has no directory.
277
 
        if old_new_root in self._new_contents:
278
 
            self.cancel_creation(old_new_root)
279
 
        else:
280
 
            self.delete_contents(old_new_root)
281
 
 
282
 
        # prevent deletion of root directory.
283
 
        if self._new_root in self._removed_contents:
284
 
            self.cancel_deletion(self._new_root)
285
 
 
286
 
        # destroy path info for old_new_root.
287
 
        del self._new_parent[old_new_root]
288
 
        del self._new_name[old_new_root]
 
211
        if inventory_id is None:
 
212
            raise ValueError('None is not a valid file id')
 
213
        path = self._tree.id2path(inventory_id)
 
214
        return self.trans_id_tree_path(path)
289
215
 
290
216
    def trans_id_file_id(self, file_id):
291
217
        """Determine or set the transaction id associated with a file ID.
299
225
            return self._r_new_id[file_id]
300
226
        else:
301
227
            try:
302
 
                path = self._tree.id2path(file_id)
303
 
            except errors.NoSuchId:
 
228
                self._tree.iter_entries_by_dir([file_id]).next()
 
229
            except StopIteration:
304
230
                if file_id in self._non_present_ids:
305
231
                    return self._non_present_ids[file_id]
306
232
                else:
308
234
                    self._non_present_ids[file_id] = trans_id
309
235
                    return trans_id
310
236
            else:
311
 
                return self.trans_id_tree_path(path)
 
237
                return self.trans_id_tree_file_id(file_id)
312
238
 
313
239
    def trans_id_tree_path(self, path):
314
240
        """Determine (and maybe set) the transaction ID for a tree path."""
327
253
 
328
254
    def delete_contents(self, trans_id):
329
255
        """Schedule the contents of a path entry for deletion"""
330
 
        kind = self.tree_kind(trans_id)
331
 
        if kind is not None:
332
 
            self._removed_contents.add(trans_id)
 
256
        self.tree_kind(trans_id)
 
257
        self._removed_contents.add(trans_id)
333
258
 
334
259
    def cancel_deletion(self, trans_id):
335
260
        """Cancel a scheduled deletion"""
392
317
        return sorted(FinalPaths(self).get_paths(new_ids))
393
318
 
394
319
    def _inventory_altered(self):
395
 
        """Determine which trans_ids need new Inventory entries.
396
 
 
397
 
        An new entry is needed when anything that would be reflected by an
398
 
        inventory entry changes, including file name, file_id, parent file_id,
399
 
        file kind, and the execute bit.
400
 
 
401
 
        Some care is taken to return entries with real changes, not cases
402
 
        where the value is deleted and then restored to its original value,
403
 
        but some actually unchanged values may be returned.
404
 
 
405
 
        :returns: A list of (path, trans_id) for all items requiring an
406
 
            inventory change. Ordered by path.
407
 
        """
408
 
        changed_ids = set()
409
 
        # Find entries whose file_ids are new (or changed).
410
 
        new_file_id = set(t for t in self._new_id
411
 
                          if self._new_id[t] != self.tree_file_id(t))
412
 
        for id_set in [self._new_name, self._new_parent, new_file_id,
 
320
        """Get the trans_ids and paths of files needing new inv entries."""
 
321
        new_ids = set()
 
322
        for id_set in [self._new_name, self._new_parent, self._new_id,
413
323
                       self._new_executability]:
414
 
            changed_ids.update(id_set)
415
 
        # removing implies a kind change
 
324
            new_ids.update(id_set)
416
325
        changed_kind = set(self._removed_contents)
417
 
        # so does adding
418
326
        changed_kind.intersection_update(self._new_contents)
419
 
        # Ignore entries that are already known to have changed.
420
 
        changed_kind.difference_update(changed_ids)
421
 
        #  to keep only the truly changed ones
422
 
        changed_kind = (t for t in changed_kind
423
 
                        if self.tree_kind(t) != self.final_kind(t))
424
 
        # all kind changes will alter the inventory
425
 
        changed_ids.update(changed_kind)
426
 
        # To find entries with changed parent_ids, find parents which existed,
427
 
        # but changed file_id.
428
 
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
429
 
        # Now add all their children to the set.
430
 
        for parent_trans_id in new_file_id:
431
 
            changed_ids.update(self.iter_tree_children(parent_trans_id))
432
 
        return sorted(FinalPaths(self).get_paths(changed_ids))
 
327
        changed_kind.difference_update(new_ids)
 
328
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
 
329
                        self.final_kind(t))
 
330
        new_ids.update(changed_kind)
 
331
        return sorted(FinalPaths(self).get_paths(new_ids))
433
332
 
434
333
    def final_kind(self, trans_id):
435
334
        """Determine the final file kind, after any changes applied.
436
335
 
437
 
        :return: None if the file does not exist/has no contents.  (It is
438
 
            conceivable that a path would be created without the corresponding
439
 
            contents insertion command)
 
336
        Raises NoSuchFile if the file does not exist/has no contents.
 
337
        (It is conceivable that a path would be created without the
 
338
        corresponding contents insertion command)
440
339
        """
441
340
        if trans_id in self._new_contents:
442
341
            return self._new_contents[trans_id]
443
342
        elif trans_id in self._removed_contents:
444
 
            return None
 
343
            raise NoSuchFile(None)
445
344
        else:
446
345
            return self.tree_kind(trans_id)
447
346
 
448
 
    def tree_path(self, trans_id):
449
 
        """Determine the tree path associated with the trans_id."""
450
 
        return self._tree_id_paths.get(trans_id)
451
 
 
452
347
    def tree_file_id(self, trans_id):
453
348
        """Determine the file id associated with the trans_id in the tree"""
454
 
        path = self.tree_path(trans_id)
455
 
        if path is None:
 
349
        try:
 
350
            path = self._tree_id_paths[trans_id]
 
351
        except KeyError:
 
352
            # the file is a new, unversioned file, or invalid trans_id
456
353
            return None
457
354
        # the file is old; the old id is still valid
458
355
        if self._new_root == trans_id:
480
377
        file_id = self.tree_file_id(trans_id)
481
378
        if file_id is not None:
482
379
            return file_id
483
 
        for key, value in viewitems(self._non_present_ids):
 
380
        for key, value in self._non_present_ids.iteritems():
484
381
            if value == trans_id:
485
382
                return key
486
383
 
510
407
        Only new paths and parents of tree files with assigned ids are used.
511
408
        """
512
409
        by_parent = {}
513
 
        items = list(viewitems(self._new_parent))
514
 
        items.extend((t, self.final_parent(t))
515
 
            for t in list(self._tree_id_paths))
 
410
        items = list(self._new_parent.iteritems())
 
411
        items.extend((t, self.final_parent(t)) for t in
 
412
                      self._tree_id_paths.keys())
516
413
        for trans_id, parent_id in items:
517
414
            if parent_id not in by_parent:
518
415
                by_parent[parent_id] = set()
556
453
        Active parents are those which gain children, and those which are
557
454
        removed.  This is a necessary first step in detecting conflicts.
558
455
        """
559
 
        parents = list(self.by_parent())
 
456
        parents = self.by_parent().keys()
560
457
        parents.extend([t for t in self._removed_contents if
561
458
                        self.tree_kind(t) == 'directory'])
562
459
        for trans_id in self._removed_id:
563
 
            path = self.tree_path(trans_id)
564
 
            if path is not None:
565
 
                if self._tree.stored_kind(path) == 'directory':
 
460
            file_id = self.tree_file_id(trans_id)
 
461
            if file_id is not None:
 
462
                if self._tree.inventory[file_id].kind == 'directory':
566
463
                    parents.append(trans_id)
567
464
            elif self.tree_kind(trans_id) == 'directory':
568
465
                parents.append(trans_id)
571
468
            # ensure that all children are registered with the transaction
572
469
            list(self.iter_tree_children(parent_id))
573
470
 
574
 
    def _has_named_child(self, name, parent_id, known_children):
575
 
        """Does a parent already have a name child.
576
 
 
577
 
        :param name: The searched for name.
578
 
 
579
 
        :param parent_id: The parent for which the check is made.
580
 
 
581
 
        :param known_children: The already known children. This should have
582
 
            been recently obtained from `self.by_parent.get(parent_id)`
583
 
            (or will be if None is passed).
584
 
        """
585
 
        if known_children is None:
586
 
            known_children = self.by_parent().get(parent_id, [])
587
 
        for child in known_children:
 
471
    def has_named_child(self, by_parent, parent_id, name):
 
472
        try:
 
473
            children = by_parent[parent_id]
 
474
        except KeyError:
 
475
            children = []
 
476
        for child in children:
588
477
            if self.final_name(child) == name:
589
478
                return True
590
 
        parent_path = self._tree_id_paths.get(parent_id, None)
591
 
        if parent_path is None:
592
 
            # No parent... no children
 
479
        try:
 
480
            path = self._tree_id_paths[parent_id]
 
481
        except KeyError:
593
482
            return False
594
 
        child_path = joinpath(parent_path, name)
595
 
        child_id = self._tree_path_ids.get(child_path, None)
 
483
        childpath = joinpath(path, name)
 
484
        child_id = self._tree_path_ids.get(childpath)
596
485
        if child_id is None:
597
 
            # Not known by the tree transform yet, check the filesystem
598
 
            return osutils.lexists(self._tree.abspath(child_path))
 
486
            return lexists(self._tree.abspath(childpath))
599
487
        else:
600
 
            raise AssertionError('child_id is missing: %s, %s, %s'
601
 
                                 % (name, parent_id, child_id))
602
 
 
603
 
    def _available_backup_name(self, name, target_id):
604
 
        """Find an available backup name.
605
 
 
606
 
        :param name: The basename of the file.
607
 
 
608
 
        :param target_id: The directory trans_id where the backup should 
609
 
            be placed.
610
 
        """
611
 
        known_children = self.by_parent().get(target_id, [])
612
 
        return osutils.available_backup_name(
613
 
            name,
614
 
            lambda base: self._has_named_child(
615
 
                base, target_id, known_children))
 
488
            if self.final_parent(child_id) != parent_id:
 
489
                return False
 
490
            if child_id in self._removed_contents:
 
491
                # XXX What about dangling file-ids?
 
492
                return False
 
493
            else:
 
494
                return True
616
495
 
617
496
    def _parent_loops(self):
618
497
        """No entry should be its own ancestor"""
620
499
        for trans_id in self._new_parent:
621
500
            seen = set()
622
501
            parent_id = trans_id
623
 
            while parent_id != ROOT_PARENT:
 
502
            while parent_id is not ROOT_PARENT:
624
503
                seen.add(parent_id)
625
504
                try:
626
505
                    parent_id = self.final_parent(parent_id)
635
514
    def _unversioned_parents(self, by_parent):
636
515
        """If parent directories are versioned, children must be versioned."""
637
516
        conflicts = []
638
 
        for parent_id, children in viewitems(by_parent):
639
 
            if parent_id == ROOT_PARENT:
 
517
        for parent_id, children in by_parent.iteritems():
 
518
            if parent_id is ROOT_PARENT:
640
519
                continue
641
520
            if self.final_file_id(parent_id) is not None:
642
521
                continue
652
531
        However, existing entries with no contents are okay.
653
532
        """
654
533
        conflicts = []
655
 
        for trans_id in self._new_id:
656
 
            kind = self.final_kind(trans_id)
657
 
            if kind is None:
 
534
        for trans_id in self._new_id.iterkeys():
 
535
            try:
 
536
                kind = self.final_kind(trans_id)
 
537
            except NoSuchFile:
658
538
                conflicts.append(('versioning no contents', trans_id))
659
539
                continue
660
 
            if not self._tree.versionable_kind(kind):
 
540
            if not InventoryEntry.versionable_kind(kind):
661
541
                conflicts.append(('versioning bad kind', trans_id, kind))
662
542
        return conflicts
663
543
 
674
554
            if self.final_file_id(trans_id) is None:
675
555
                conflicts.append(('unversioned executability', trans_id))
676
556
            else:
677
 
                if self.final_kind(trans_id) != "file":
 
557
                try:
 
558
                    non_file = self.final_kind(trans_id) != "file"
 
559
                except NoSuchFile:
 
560
                    non_file = True
 
561
                if non_file is True:
678
562
                    conflicts.append(('non-file executability', trans_id))
679
563
        return conflicts
680
564
 
682
566
        """Check for overwrites (not permitted on Win32)"""
683
567
        conflicts = []
684
568
        for trans_id in self._new_contents:
685
 
            if self.tree_kind(trans_id) is None:
 
569
            try:
 
570
                self.tree_kind(trans_id)
 
571
            except NoSuchFile:
686
572
                continue
687
573
            if trans_id not in self._removed_contents:
688
574
                conflicts.append(('overwrite', trans_id,
694
580
        conflicts = []
695
581
        if (self._new_name, self._new_parent) == ({}, {}):
696
582
            return conflicts
697
 
        for children in viewvalues(by_parent):
698
 
            name_ids = []
699
 
            for child_tid in children:
700
 
                name = self.final_name(child_tid)
701
 
                if name is not None:
702
 
                    # Keep children only if they still exist in the end
703
 
                    if not self._case_sensitive_target:
704
 
                        name = name.lower()
705
 
                    name_ids.append((name, child_tid))
 
583
        for children in by_parent.itervalues():
 
584
            name_ids = [(self.final_name(t), t) for t in children]
 
585
            if not self._case_sensitive_target:
 
586
                name_ids = [(n.lower(), t) for n, t in name_ids]
706
587
            name_ids.sort()
707
588
            last_name = None
708
589
            last_trans_id = None
709
590
            for name, trans_id in name_ids:
710
 
                kind = self.final_kind(trans_id)
 
591
                try:
 
592
                    kind = self.final_kind(trans_id)
 
593
                except NoSuchFile:
 
594
                    kind = None
711
595
                file_id = self.final_file_id(trans_id)
712
596
                if kind is None and file_id is None:
713
597
                    continue
725
609
                                self._removed_id))
726
610
        all_ids = self._tree.all_file_ids()
727
611
        active_tree_ids = all_ids.difference(removed_tree_ids)
728
 
        for trans_id, file_id in viewitems(self._new_id):
 
612
        for trans_id, file_id in self._new_id.iteritems():
729
613
            if file_id in active_tree_ids:
730
 
                path = self._tree.id2path(file_id)
731
 
                old_trans_id = self.trans_id_tree_path(path)
 
614
                old_trans_id = self.trans_id_tree_file_id(file_id)
732
615
                conflicts.append(('duplicate id', old_trans_id, trans_id))
733
616
        return conflicts
734
617
 
735
618
    def _parent_type_conflicts(self, by_parent):
736
 
        """Children must have a directory parent"""
 
619
        """parents must have directory 'contents'."""
737
620
        conflicts = []
738
 
        for parent_id, children in viewitems(by_parent):
739
 
            if parent_id == ROOT_PARENT:
740
 
                continue
741
 
            no_children = True
742
 
            for child_id in children:
743
 
                if self.final_kind(child_id) is not None:
744
 
                    no_children = False
745
 
                    break
746
 
            if no_children:
747
 
                continue
748
 
            # There is at least a child, so we need an existing directory to
749
 
            # contain it.
750
 
            kind = self.final_kind(parent_id)
 
621
        for parent_id, children in by_parent.iteritems():
 
622
            if parent_id is ROOT_PARENT:
 
623
                continue
 
624
            if not self._any_contents(children):
 
625
                continue
 
626
            for child in children:
 
627
                try:
 
628
                    self.final_kind(child)
 
629
                except NoSuchFile:
 
630
                    continue
 
631
            try:
 
632
                kind = self.final_kind(parent_id)
 
633
            except NoSuchFile:
 
634
                kind = None
751
635
            if kind is None:
752
 
                # The directory will be deleted
753
636
                conflicts.append(('missing parent', parent_id))
754
637
            elif kind != "directory":
755
 
                # Meh, we need a *directory* to put something in it
756
638
                conflicts.append(('non-directory parent', parent_id))
757
639
        return conflicts
758
640
 
 
641
    def _any_contents(self, trans_ids):
 
642
        """Return true if any of the trans_ids, will have contents."""
 
643
        for trans_id in trans_ids:
 
644
            try:
 
645
                kind = self.final_kind(trans_id)
 
646
            except NoSuchFile:
 
647
                continue
 
648
            return True
 
649
        return False
 
650
 
759
651
    def _set_executability(self, path, trans_id):
760
652
        """Set the executability of versioned files """
761
 
        if self._tree._supports_executable():
 
653
        if supports_executable():
762
654
            new_executability = self._new_executability[trans_id]
763
655
            abspath = self._tree.abspath(path)
764
656
            current_mode = os.stat(abspath).st_mode
765
657
            if new_executability:
766
658
                umask = os.umask(0)
767
659
                os.umask(umask)
768
 
                to_mode = current_mode | (0o100 & ~umask)
 
660
                to_mode = current_mode | (0100 & ~umask)
769
661
                # 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
 
662
                if current_mode & 0004:
 
663
                    to_mode |= 0001 & ~umask
 
664
                if current_mode & 0040:
 
665
                    to_mode |= 0010 & ~umask
774
666
            else:
775
 
                to_mode = current_mode & ~0o111
776
 
            osutils.chmod_if_possible(abspath, to_mode)
 
667
                to_mode = current_mode & ~0111
 
668
            os.chmod(abspath, to_mode)
777
669
 
778
670
    def _new_entry(self, name, parent_id, file_id):
779
671
        """Helper function to create a new filesystem entry."""
783
675
        return trans_id
784
676
 
785
677
    def new_file(self, name, parent_id, contents, file_id=None,
786
 
                 executable=None, sha1=None):
 
678
                 executable=None):
787
679
        """Convenience method to create files.
788
680
 
789
681
        name is the name of the file to create.
796
688
        trans_id = self._new_entry(name, parent_id, file_id)
797
689
        # TODO: rather than scheduling a set_executable call,
798
690
        # have create_file create the file with the right mode.
799
 
        self.create_file(contents, trans_id, sha1=sha1)
 
691
        self.create_file(contents, trans_id)
800
692
        if executable is not None:
801
693
            self.set_executability(executable, trans_id)
802
694
        return trans_id
825
717
        self.create_symlink(target, trans_id)
826
718
        return trans_id
827
719
 
828
 
    def new_orphan(self, trans_id, parent_id):
829
 
        """Schedule an item to be orphaned.
830
 
 
831
 
        When a directory is about to be removed, its children, if they are not
832
 
        versioned are moved out of the way: they don't have a parent anymore.
833
 
 
834
 
        :param trans_id: The trans_id of the existing item.
835
 
        :param parent_id: The parent trans_id of the item.
836
 
        """
837
 
        raise NotImplementedError(self.new_orphan)
838
 
 
839
 
    def _get_potential_orphans(self, dir_id):
840
 
        """Find the potential orphans in a directory.
841
 
 
842
 
        A directory can't be safely deleted if there are versioned files in it.
843
 
        If all the contained files are unversioned then they can be orphaned.
844
 
 
845
 
        The 'None' return value means that the directory contains at least one
846
 
        versioned file and should not be deleted.
847
 
 
848
 
        :param dir_id: The directory trans id.
849
 
 
850
 
        :return: A list of the orphan trans ids or None if at least one
851
 
             versioned file is present.
852
 
        """
853
 
        orphans = []
854
 
        # Find the potential orphans, stop if one item should be kept
855
 
        for child_tid in self.by_parent()[dir_id]:
856
 
            if child_tid in self._removed_contents:
857
 
                # The child is removed as part of the transform. Since it was
858
 
                # versioned before, it's not an orphan
859
 
                continue
860
 
            elif self.final_file_id(child_tid) is None:
861
 
                # The child is not versioned
862
 
                orphans.append(child_tid)
863
 
            else:
864
 
                # We have a versioned file here, searching for orphans is
865
 
                # meaningless.
866
 
                orphans = None
867
 
                break
868
 
        return orphans
869
 
 
870
720
    def _affected_ids(self):
871
721
        """Return the set of transform ids affected by the transform"""
872
722
        trans_ids = set(self._removed_id)
873
 
        trans_ids.update(self._new_id)
 
723
        trans_ids.update(self._new_id.keys())
874
724
        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)
 
725
        trans_ids.update(self._new_contents.keys())
 
726
        trans_ids.update(self._new_executability.keys())
 
727
        trans_ids.update(self._new_name.keys())
 
728
        trans_ids.update(self._new_parent.keys())
879
729
        return trans_ids
880
730
 
881
731
    def _get_file_id_maps(self):
894
744
                to_trans_ids[to_file_id] = trans_id
895
745
        return from_trans_ids, to_trans_ids
896
746
 
897
 
    def _from_file_data(self, from_trans_id, from_versioned, from_path):
 
747
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
898
748
        """Get data about a file in the from (tree) state
899
749
 
900
750
        Return a (name, parent, kind, executable) tuple
902
752
        from_path = self._tree_id_paths.get(from_trans_id)
903
753
        if from_versioned:
904
754
            # get data from working tree if versioned
905
 
            from_entry = next(self._tree.iter_entries_by_dir(
906
 
                    specific_files=[from_path]))[1]
 
755
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
907
756
            from_name = from_entry.name
908
757
            from_parent = from_entry.parent_id
909
758
        else:
932
781
        Return a (name, parent, kind, executable) tuple
933
782
        """
934
783
        to_name = self.final_name(to_trans_id)
935
 
        to_kind = self.final_kind(to_trans_id)
 
784
        try:
 
785
            to_kind = self.final_kind(to_trans_id)
 
786
        except NoSuchFile:
 
787
            to_kind = None
936
788
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
937
789
        if to_trans_id in self._new_executability:
938
790
            to_executable = self._new_executability[to_trans_id]
956
808
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
957
809
        results = []
958
810
        # Now iterate through all active file_ids
959
 
        for file_id in set(from_trans_ids).union(to_trans_ids):
 
811
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
960
812
            modified = False
961
813
            from_trans_id = from_trans_ids.get(file_id)
962
814
            # find file ids, and determine versioning state
972
824
            else:
973
825
                to_versioned = True
974
826
 
 
827
            from_name, from_parent, from_kind, from_executable = \
 
828
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
829
 
 
830
            to_name, to_parent, to_kind, to_executable = \
 
831
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
832
 
975
833
            if not from_versioned:
976
834
                from_path = None
977
835
            else:
980
838
                to_path = None
981
839
            else:
982
840
                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
841
            if from_kind != to_kind:
991
842
                modified = True
992
843
            elif to_kind in ('file', 'symlink') and (
1013
864
        """
1014
865
        return _PreviewTree(self)
1015
866
 
1016
 
    def commit(self, branch, message, merge_parents=None, strict=False,
1017
 
               timestamp=None, timezone=None, committer=None, authors=None,
1018
 
               revprops=None, revision_id=None):
 
867
    def commit(self, branch, message, merge_parents=None, strict=False):
1019
868
        """Commit the result of this TreeTransform to a branch.
1020
869
 
1021
870
        :param branch: The branch to commit to.
1022
871
        :param message: The message to attach to the commit.
1023
 
        :param merge_parents: Additional parent revision-ids specified by
1024
 
            pending merges.
1025
 
        :param strict: If True, abort the commit if there are unversioned
1026
 
            files.
1027
 
        :param timestamp: if not None, seconds-since-epoch for the time and
1028
 
            date.  (May be a float.)
1029
 
        :param timezone: Optional timezone for timestamp, as an offset in
1030
 
            seconds.
1031
 
        :param committer: Optional committer in email-id format.
1032
 
            (e.g. "J Random Hacker <jrandom@example.com>")
1033
 
        :param authors: Optional list of authors in email-id format.
1034
 
        :param revprops: Optional dictionary of revision properties.
1035
 
        :param revision_id: Optional revision id.  (Specifying a revision-id
1036
 
            may reduce performance for some non-native formats.)
 
872
        :param merge_parents: Additional parents specified by pending merges.
1037
873
        :return: The revision_id of the revision committed.
1038
874
        """
1039
875
        self._check_malformed()
1056
892
        if self._tree.get_revision_id() != last_rev_id:
1057
893
            raise ValueError('TreeTransform not based on branch basis: %s' %
1058
894
                             self._tree.get_revision_id())
1059
 
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
1060
 
        builder = branch.get_commit_builder(parent_ids,
1061
 
                                            timestamp=timestamp,
1062
 
                                            timezone=timezone,
1063
 
                                            committer=committer,
1064
 
                                            revprops=revprops,
1065
 
                                            revision_id=revision_id)
 
895
        builder = branch.get_commit_builder(parent_ids)
1066
896
        preview = self.get_preview_tree()
1067
897
        list(builder.record_iter_changes(preview, last_rev_id,
1068
898
                                         self.iter_changes()))
1072
902
        return revision_id
1073
903
 
1074
904
    def _text_parent(self, trans_id):
1075
 
        path = self.tree_path(trans_id)
 
905
        file_id = self.tree_file_id(trans_id)
1076
906
        try:
1077
 
            if path is None or self._tree.kind(path) != 'file':
 
907
            if file_id is None or self._tree.kind(file_id) != 'file':
1078
908
                return None
1079
909
        except errors.NoSuchFile:
1080
910
            return None
1081
 
        return path
 
911
        return file_id
1082
912
 
1083
913
    def _get_parents_texts(self, trans_id):
1084
914
        """Get texts for compression parents of this file."""
1085
 
        path = self._text_parent(trans_id)
1086
 
        if path is None:
 
915
        file_id = self._text_parent(trans_id)
 
916
        if file_id is None:
1087
917
            return ()
1088
 
        return (self._tree.get_file_text(path),)
 
918
        return (self._tree.get_file_text(file_id),)
1089
919
 
1090
920
    def _get_parents_lines(self, trans_id):
1091
921
        """Get lines for compression parents of this file."""
1092
 
        path = self._text_parent(trans_id)
1093
 
        if path is None:
 
922
        file_id = self._text_parent(trans_id)
 
923
        if file_id is None:
1094
924
            return ()
1095
 
        return (self._tree.get_file_lines(path),)
 
925
        return (self._tree.get_file_lines(file_id),)
1096
926
 
1097
927
    def serialize(self, serializer):
1098
928
        """Serialize this TreeTransform.
1100
930
        :param serializer: A Serialiser like pack.ContainerSerializer.
1101
931
        """
1102
932
        new_name = dict((k, v.encode('utf-8')) for k, v in
1103
 
                        viewitems(self._new_name))
 
933
                        self._new_name.items())
1104
934
        new_executability = dict((k, int(v)) for k, v in
1105
 
                                 viewitems(self._new_executability))
 
935
                                 self._new_executability.items())
1106
936
        tree_path_ids = dict((k.encode('utf-8'), v)
1107
 
                             for k, v in viewitems(self._tree_path_ids))
 
937
                             for k, v in self._tree_path_ids.items())
1108
938
        attribs = {
1109
 
            b'_id_number': self._id_number,
1110
 
            b'_new_name': new_name,
1111
 
            b'_new_parent': self._new_parent,
1112
 
            b'_new_executability': new_executability,
1113
 
            b'_new_id': self._new_id,
1114
 
            b'_tree_path_ids': tree_path_ids,
1115
 
            b'_removed_id': list(self._removed_id),
1116
 
            b'_removed_contents': list(self._removed_contents),
1117
 
            b'_non_present_ids': self._non_present_ids,
 
939
            '_id_number': self._id_number,
 
940
            '_new_name': new_name,
 
941
            '_new_parent': self._new_parent,
 
942
            '_new_executability': new_executability,
 
943
            '_new_id': self._new_id,
 
944
            '_tree_path_ids': tree_path_ids,
 
945
            '_removed_id': list(self._removed_id),
 
946
            '_removed_contents': list(self._removed_contents),
 
947
            '_non_present_ids': self._non_present_ids,
1118
948
            }
1119
949
        yield serializer.bytes_record(bencode.bencode(attribs),
1120
 
                                      ((b'attribs',),))
1121
 
        for trans_id, kind in viewitems(self._new_contents):
 
950
                                      (('attribs',),))
 
951
        for trans_id, kind in self._new_contents.items():
1122
952
            if kind == 'file':
1123
 
                with open(self._limbo_name(trans_id), 'rb') as cur_file:
1124
 
                    lines = cur_file.readlines()
 
953
                lines = osutils.chunks_to_lines(
 
954
                    self._read_file_chunks(trans_id))
1125
955
                parents = self._get_parents_lines(trans_id)
1126
956
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1127
 
                content = b''.join(mpdiff.to_patch())
 
957
                content = ''.join(mpdiff.to_patch())
1128
958
            if kind == 'directory':
1129
 
                content = b''
 
959
                content = ''
1130
960
            if kind == 'symlink':
1131
961
                content = self._read_symlink_target(trans_id)
1132
962
            yield serializer.bytes_record(content, ((trans_id, kind),))
1137
967
        :param records: An iterable of (names, content) tuples, as per
1138
968
            pack.ContainerPushParser.
1139
969
        """
1140
 
        names, content = next(records)
 
970
        names, content = records.next()
1141
971
        attribs = bencode.bdecode(content)
1142
 
        self._id_number = attribs[b'_id_number']
 
972
        self._id_number = attribs['_id_number']
1143
973
        self._new_name = dict((k, v.decode('utf-8'))
1144
 
                              for k, v in viewitems(attribs[b'_new_name']))
1145
 
        self._new_parent = attribs[b'_new_parent']
1146
 
        self._new_executability = dict((k, bool(v))
1147
 
            for k, v in viewitems(attribs[b'_new_executability']))
1148
 
        self._new_id = attribs[b'_new_id']
1149
 
        self._r_new_id = dict((v, k) for k, v in viewitems(self._new_id))
 
974
                            for k, v in attribs['_new_name'].items())
 
975
        self._new_parent = attribs['_new_parent']
 
976
        self._new_executability = dict((k, bool(v)) for k, v in
 
977
            attribs['_new_executability'].items())
 
978
        self._new_id = attribs['_new_id']
 
979
        self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1150
980
        self._tree_path_ids = {}
1151
981
        self._tree_id_paths = {}
1152
 
        for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
 
982
        for bytepath, trans_id in attribs['_tree_path_ids'].items():
1153
983
            path = bytepath.decode('utf-8')
1154
984
            self._tree_path_ids[path] = trans_id
1155
985
            self._tree_id_paths[trans_id] = path
1156
 
        self._removed_id = set(attribs[b'_removed_id'])
1157
 
        self._removed_contents = set(attribs[b'_removed_contents'])
1158
 
        self._non_present_ids = attribs[b'_non_present_ids']
 
986
        self._removed_id = set(attribs['_removed_id'])
 
987
        self._removed_contents = set(attribs['_removed_contents'])
 
988
        self._non_present_ids = attribs['_non_present_ids']
1159
989
        for ((trans_id, kind),), content in records:
1160
990
            if kind == 'file':
1161
991
                mpdiff = multiparent.MultiParent.from_patch(content)
1170
1000
class DiskTreeTransform(TreeTransformBase):
1171
1001
    """Tree transform storing its contents on disk."""
1172
1002
 
1173
 
    def __init__(self, tree, limbodir, pb=None,
 
1003
    def __init__(self, tree, limbodir, pb=DummyProgress(),
1174
1004
                 case_sensitive=True):
1175
1005
        """Constructor.
1176
1006
        :param tree: The tree that will be transformed, but not necessarily
1177
1007
            the output tree.
1178
1008
        :param limbodir: A directory where new files can be stored until
1179
1009
            they are installed in their proper places
1180
 
        :param pb: ignored
 
1010
        :param pb: A ProgressBar indicating how much progress is being made
1181
1011
        :param case_sensitive: If True, the target of the transform is
1182
1012
            case sensitive, not just case preserving.
1183
1013
        """
1186
1016
        self._deletiondir = None
1187
1017
        # A mapping of transform ids to their limbo filename
1188
1018
        self._limbo_files = {}
1189
 
        self._possibly_stale_limbo_files = set()
1190
1019
        # A mapping of transform ids to a set of the transform ids of children
1191
1020
        # that their limbo directory has
1192
1021
        self._limbo_children = {}
1194
1023
        self._limbo_children_names = {}
1195
1024
        # List of transform ids that need to be renamed from limbo into place
1196
1025
        self._needs_rename = set()
1197
 
        self._creation_mtime = None
1198
1026
 
1199
1027
    def finalize(self):
1200
1028
        """Release the working tree lock, if held, clean up limbo dir.
1205
1033
        if self._tree is None:
1206
1034
            return
1207
1035
        try:
1208
 
            limbo_paths = list(viewvalues(self._limbo_files))
1209
 
            limbo_paths.extend(self._possibly_stale_limbo_files)
1210
 
            limbo_paths.sort(reverse=True)
1211
 
            for path in limbo_paths:
1212
 
                try:
1213
 
                    delete_any(path)
1214
 
                except OSError as e:
1215
 
                    if e.errno != errno.ENOENT:
1216
 
                        raise
1217
 
                    # XXX: warn? perhaps we just got interrupted at an
1218
 
                    # inconvenient moment, but perhaps files are disappearing
1219
 
                    # from under us?
 
1036
            entries = [(self._limbo_name(t), t, k) for t, k in
 
1037
                       self._new_contents.iteritems()]
 
1038
            entries.sort(reverse=True)
 
1039
            for path, trans_id, kind in entries:
 
1040
                delete_any(path)
1220
1041
            try:
1221
1042
                delete_any(self._limbodir)
1222
1043
            except OSError:
1230
1051
        finally:
1231
1052
            TreeTransformBase.finalize(self)
1232
1053
 
1233
 
    def _limbo_supports_executable(self):
1234
 
        """Check if the limbo path supports the executable bit."""
1235
 
        # FIXME: Check actual file system capabilities of limbodir
1236
 
        return osutils.supports_executable()
1237
 
 
1238
1054
    def _limbo_name(self, trans_id):
1239
1055
        """Generate the limbo name of a file"""
1240
1056
        limbo_name = self._limbo_files.get(trans_id)
1241
 
        if limbo_name is None:
1242
 
            limbo_name = self._generate_limbo_path(trans_id)
1243
 
            self._limbo_files[trans_id] = limbo_name
 
1057
        if limbo_name is not None:
 
1058
            return limbo_name
 
1059
        parent = self._new_parent.get(trans_id)
 
1060
        # if the parent directory is already in limbo (e.g. when building a
 
1061
        # tree), choose a limbo name inside the parent, to reduce further
 
1062
        # renames.
 
1063
        use_direct_path = False
 
1064
        if self._new_contents.get(parent) == 'directory':
 
1065
            filename = self._new_name.get(trans_id)
 
1066
            if filename is not None:
 
1067
                if parent not in self._limbo_children:
 
1068
                    self._limbo_children[parent] = set()
 
1069
                    self._limbo_children_names[parent] = {}
 
1070
                    use_direct_path = True
 
1071
                # the direct path can only be used if no other file has
 
1072
                # already taken this pathname, i.e. if the name is unused, or
 
1073
                # if it is already associated with this trans_id.
 
1074
                elif self._case_sensitive_target:
 
1075
                    if (self._limbo_children_names[parent].get(filename)
 
1076
                        in (trans_id, None)):
 
1077
                        use_direct_path = True
 
1078
                else:
 
1079
                    for l_filename, l_trans_id in\
 
1080
                        self._limbo_children_names[parent].iteritems():
 
1081
                        if l_trans_id == trans_id:
 
1082
                            continue
 
1083
                        if l_filename.lower() == filename.lower():
 
1084
                            break
 
1085
                    else:
 
1086
                        use_direct_path = True
 
1087
 
 
1088
        if use_direct_path:
 
1089
            limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1090
            self._limbo_children[parent].add(trans_id)
 
1091
            self._limbo_children_names[parent][filename] = trans_id
 
1092
        else:
 
1093
            limbo_name = pathjoin(self._limbodir, trans_id)
 
1094
            self._needs_rename.add(trans_id)
 
1095
        self._limbo_files[trans_id] = limbo_name
1244
1096
        return limbo_name
1245
1097
 
1246
 
    def _generate_limbo_path(self, trans_id):
1247
 
        """Generate a limbo path using the trans_id as the relative path.
1248
 
 
1249
 
        This is suitable as a fallback, and when the transform should not be
1250
 
        sensitive to the path encoding of the limbo directory.
1251
 
        """
1252
 
        self._needs_rename.add(trans_id)
1253
 
        return pathjoin(self._limbodir, trans_id)
1254
 
 
1255
1098
    def adjust_path(self, name, parent, trans_id):
1256
1099
        previous_parent = self._new_parent.get(trans_id)
1257
1100
        previous_name = self._new_name.get(trans_id)
1259
1102
        if (trans_id in self._limbo_files and
1260
1103
            trans_id not in self._needs_rename):
1261
1104
            self._rename_in_limbo([trans_id])
1262
 
            if previous_parent != parent:
1263
 
                self._limbo_children[previous_parent].remove(trans_id)
1264
 
            if previous_parent != parent or previous_name != name:
1265
 
                del self._limbo_children_names[previous_parent][previous_name]
 
1105
            self._limbo_children[previous_parent].remove(trans_id)
 
1106
            del self._limbo_children_names[previous_parent][previous_name]
1266
1107
 
1267
1108
    def _rename_in_limbo(self, trans_ids):
1268
1109
        """Fix limbo names so that the right final path is produced.
1276
1117
        entries from _limbo_files, because they are now stale.
1277
1118
        """
1278
1119
        for trans_id in trans_ids:
1279
 
            old_path = self._limbo_files[trans_id]
1280
 
            self._possibly_stale_limbo_files.add(old_path)
1281
 
            del self._limbo_files[trans_id]
 
1120
            old_path = self._limbo_files.pop(trans_id)
1282
1121
            if trans_id not in self._new_contents:
1283
1122
                continue
1284
1123
            new_path = self._limbo_name(trans_id)
1285
1124
            os.rename(old_path, new_path)
1286
 
            self._possibly_stale_limbo_files.remove(old_path)
1287
1125
            for descendant in self._limbo_descendants(trans_id):
1288
1126
                desc_path = self._limbo_files[descendant]
1289
1127
                desc_path = new_path + desc_path[len(old_path):]
1296
1134
            descendants.update(self._limbo_descendants(descendant))
1297
1135
        return descendants
1298
1136
 
1299
 
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
 
1137
    def create_file(self, contents, trans_id, mode_id=None):
1300
1138
        """Schedule creation of a new file.
1301
1139
 
1302
 
        :seealso: new_file.
1303
 
 
1304
 
        :param contents: an iterator of strings, all of which will be written
1305
 
            to the target destination.
1306
 
        :param trans_id: TreeTransform handle
1307
 
        :param mode_id: If not None, force the mode of the target file to match
1308
 
            the mode of the object referenced by mode_id.
1309
 
            Otherwise, we will try to preserve mode bits of an existing file.
1310
 
        :param sha1: If the sha1 of this content is already known, pass it in.
1311
 
            We can use it to prevent future sha1 computations.
 
1140
        See also new_file.
 
1141
 
 
1142
        Contents is an iterator of strings, all of which will be written
 
1143
        to the target destination.
 
1144
 
 
1145
        New file takes the permissions of any existing file with that id,
 
1146
        unless mode_id is specified.
1312
1147
        """
1313
1148
        name = self._limbo_name(trans_id)
1314
 
        with open(name, 'wb') as f:
1315
 
            unique_add(self._new_contents, trans_id, 'file')
 
1149
        f = open(name, 'wb')
 
1150
        try:
 
1151
            try:
 
1152
                unique_add(self._new_contents, trans_id, 'file')
 
1153
            except:
 
1154
                # Clean up the file, it never got registered so
 
1155
                # TreeTransform.finalize() won't clean it up.
 
1156
                f.close()
 
1157
                os.unlink(name)
 
1158
                raise
 
1159
 
1316
1160
            f.writelines(contents)
1317
 
        self._set_mtime(name)
 
1161
        finally:
 
1162
            f.close()
1318
1163
        self._set_mode(trans_id, mode_id, S_ISREG)
1319
 
        # It is unfortunate we have to use lstat instead of fstat, but we just
1320
 
        # used utime and chmod on the file, so we need the accurate final
1321
 
        # details.
1322
 
        if sha1 is not None:
1323
 
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
 
1164
 
 
1165
    def _read_file_chunks(self, trans_id):
 
1166
        cur_file = open(self._limbo_name(trans_id), 'rb')
 
1167
        try:
 
1168
            return cur_file.readlines()
 
1169
        finally:
 
1170
            cur_file.close()
1324
1171
 
1325
1172
    def _read_symlink_target(self, trans_id):
1326
1173
        return os.readlink(self._limbo_name(trans_id))
1327
1174
 
1328
 
    def _set_mtime(self, path):
1329
 
        """All files that are created get the same mtime.
1330
 
 
1331
 
        This time is set by the first object to be created.
1332
 
        """
1333
 
        if self._creation_mtime is None:
1334
 
            self._creation_mtime = time.time()
1335
 
        os.utime(path, (self._creation_mtime, self._creation_mtime))
1336
 
 
1337
1175
    def create_hardlink(self, path, trans_id):
1338
1176
        """Schedule creation of a hard link"""
1339
1177
        name = self._limbo_name(trans_id)
1340
1178
        try:
1341
1179
            os.link(path, name)
1342
 
        except OSError as e:
 
1180
        except OSError, e:
1343
1181
            if e.errno != errno.EPERM:
1344
1182
                raise
1345
1183
            raise errors.HardLinkNotSupported(path)
1378
1216
    def cancel_creation(self, trans_id):
1379
1217
        """Cancel the creation of new file contents."""
1380
1218
        del self._new_contents[trans_id]
1381
 
        if trans_id in self._observed_sha1s:
1382
 
            del self._observed_sha1s[trans_id]
1383
1219
        children = self._limbo_children.get(trans_id)
1384
1220
        # if this is a limbo directory with children, move them before removing
1385
1221
        # the directory
1389
1225
            del self._limbo_children_names[trans_id]
1390
1226
        delete_any(self._limbo_name(trans_id))
1391
1227
 
1392
 
    def new_orphan(self, trans_id, parent_id):
1393
 
        conf = self._tree.get_config_stack()
1394
 
        handle_orphan = conf.get('transform.orphan_policy')
1395
 
        handle_orphan(self, trans_id, parent_id)
1396
 
 
1397
 
 
1398
 
class OrphaningError(errors.BzrError):
1399
 
 
1400
 
    # Only bugs could lead to such exception being seen by the user
1401
 
    internal_error = True
1402
 
    _fmt = "Error while orphaning %s in %s directory"
1403
 
 
1404
 
    def __init__(self, orphan, parent):
1405
 
        errors.BzrError.__init__(self)
1406
 
        self.orphan = orphan
1407
 
        self.parent = parent
1408
 
 
1409
 
 
1410
 
class OrphaningForbidden(OrphaningError):
1411
 
 
1412
 
    _fmt = "Policy: %s doesn't allow creating orphans."
1413
 
 
1414
 
    def __init__(self, policy):
1415
 
        errors.BzrError.__init__(self)
1416
 
        self.policy = policy
1417
 
 
1418
 
 
1419
 
def move_orphan(tt, orphan_id, parent_id):
1420
 
    """See TreeTransformBase.new_orphan.
1421
 
 
1422
 
    This creates a new orphan in the `brz-orphans` dir at the root of the
1423
 
    `TreeTransform`.
1424
 
 
1425
 
    :param tt: The TreeTransform orphaning `trans_id`.
1426
 
 
1427
 
    :param orphan_id: The trans id that should be orphaned.
1428
 
 
1429
 
    :param parent_id: The orphan parent trans id.
1430
 
    """
1431
 
    # Add the orphan dir if it doesn't exist
1432
 
    orphan_dir_basename = 'brz-orphans'
1433
 
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
1434
 
    if tt.final_kind(od_id) is None:
1435
 
        tt.create_directory(od_id)
1436
 
    parent_path = tt._tree_id_paths[parent_id]
1437
 
    # Find a name that doesn't exist yet in the orphan dir
1438
 
    actual_name = tt.final_name(orphan_id)
1439
 
    new_name = tt._available_backup_name(actual_name, od_id)
1440
 
    tt.adjust_path(new_name, od_id, orphan_id)
1441
 
    trace.warning('%s has been orphaned in %s'
1442
 
                  % (joinpath(parent_path, actual_name), orphan_dir_basename))
1443
 
 
1444
 
 
1445
 
def refuse_orphan(tt, orphan_id, parent_id):
1446
 
    """See TreeTransformBase.new_orphan.
1447
 
 
1448
 
    This refuses to create orphan, letting the caller handle the conflict.
1449
 
    """
1450
 
    raise OrphaningForbidden('never')
1451
 
 
1452
 
 
1453
 
orphaning_registry = registry.Registry()
1454
 
orphaning_registry.register(
1455
 
    u'conflict', refuse_orphan,
1456
 
    'Leave orphans in place and create a conflict on the directory.')
1457
 
orphaning_registry.register(
1458
 
    u'move', move_orphan,
1459
 
    'Move orphans into the brz-orphans directory.')
1460
 
orphaning_registry._set_default_key(u'conflict')
1461
 
 
1462
 
 
1463
 
opt_transform_orphan = _mod_config.RegistryOption(
1464
 
    'transform.orphan_policy', orphaning_registry,
1465
 
    help='Policy for orphaned files during transform operations.',
1466
 
    invalid='warning')
1467
 
 
1468
1228
 
1469
1229
class TreeTransform(DiskTreeTransform):
1470
1230
    """Represent a tree transformation.
1531
1291
    FileMover does not delete files until it is sure that a rollback will not
1532
1292
    happen.
1533
1293
    """
1534
 
    def __init__(self, tree, pb=None):
 
1294
    def __init__(self, tree, pb=DummyProgress()):
1535
1295
        """Note: a tree_write lock is taken on the tree.
1536
1296
 
1537
1297
        Use TreeTransform.finalize() to release the lock (can be omitted if
1538
1298
        TreeTransform.apply() called).
1539
1299
        """
1540
1300
        tree.lock_tree_write()
 
1301
 
1541
1302
        try:
1542
1303
            limbodir = urlutils.local_path_from_url(
1543
1304
                tree._transport.abspath('limbo'))
1544
 
            osutils.ensure_empty_directory_exists(
1545
 
                limbodir,
1546
 
                errors.ExistingLimbo)
 
1305
            try:
 
1306
                os.mkdir(limbodir)
 
1307
            except OSError, e:
 
1308
                if e.errno == errno.EEXIST:
 
1309
                    raise ExistingLimbo(limbodir)
1547
1310
            deletiondir = urlutils.local_path_from_url(
1548
1311
                tree._transport.abspath('pending-deletion'))
1549
 
            osutils.ensure_empty_directory_exists(
1550
 
                deletiondir,
1551
 
                errors.ExistingPendingDeletion)
 
1312
            try:
 
1313
                os.mkdir(deletiondir)
 
1314
            except OSError, e:
 
1315
                if e.errno == errno.EEXIST:
 
1316
                    raise errors.ExistingPendingDeletion(deletiondir)
1552
1317
        except:
1553
1318
            tree.unlock()
1554
1319
            raise
1583
1348
    def tree_kind(self, trans_id):
1584
1349
        """Determine the file kind in the working tree.
1585
1350
 
1586
 
        :returns: The file kind or None if the file does not exist
 
1351
        Raises NoSuchFile if the file does not exist
1587
1352
        """
1588
1353
        path = self._tree_id_paths.get(trans_id)
1589
1354
        if path is None:
1590
 
            return None
 
1355
            raise NoSuchFile(None)
1591
1356
        try:
1592
1357
            return file_kind(self._tree.abspath(path))
1593
 
        except errors.NoSuchFile:
1594
 
            return None
 
1358
        except OSError, e:
 
1359
            if e.errno != errno.ENOENT:
 
1360
                raise
 
1361
            else:
 
1362
                raise NoSuchFile(path)
1595
1363
 
1596
1364
    def _set_mode(self, trans_id, mode_id, typefunc):
1597
1365
        """Set the mode of new file contents.
1607
1375
            return
1608
1376
        try:
1609
1377
            mode = os.stat(self._tree.abspath(old_path)).st_mode
1610
 
        except OSError as e:
 
1378
        except OSError, e:
1611
1379
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
1612
1380
                # Either old_path doesn't exist, or the parent of the
1613
1381
                # target is not a directory (but will be one eventually)
1617
1385
            else:
1618
1386
                raise
1619
1387
        if typefunc(mode):
1620
 
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
 
1388
            os.chmod(self._limbo_name(trans_id), mode)
1621
1389
 
1622
1390
    def iter_tree_children(self, parent_id):
1623
1391
        """Iterate through the entry's tree children, if any"""
1627
1395
            return
1628
1396
        try:
1629
1397
            children = os.listdir(self._tree.abspath(path))
1630
 
        except OSError as e:
 
1398
        except OSError, e:
1631
1399
            if not (osutils._is_error_enotdir(e)
1632
1400
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
1633
1401
                raise
1639
1407
                continue
1640
1408
            yield self.trans_id_tree_path(childpath)
1641
1409
 
1642
 
    def _generate_limbo_path(self, trans_id):
1643
 
        """Generate a limbo path using the final path if possible.
1644
 
 
1645
 
        This optimizes the performance of applying the tree transform by
1646
 
        avoiding renames.  These renames can be avoided only when the parent
1647
 
        directory is already scheduled for creation.
1648
 
 
1649
 
        If the final path cannot be used, falls back to using the trans_id as
1650
 
        the relpath.
1651
 
        """
1652
 
        parent = self._new_parent.get(trans_id)
1653
 
        # if the parent directory is already in limbo (e.g. when building a
1654
 
        # tree), choose a limbo name inside the parent, to reduce further
1655
 
        # renames.
1656
 
        use_direct_path = False
1657
 
        if self._new_contents.get(parent) == 'directory':
1658
 
            filename = self._new_name.get(trans_id)
1659
 
            if filename is not None:
1660
 
                if parent not in self._limbo_children:
1661
 
                    self._limbo_children[parent] = set()
1662
 
                    self._limbo_children_names[parent] = {}
1663
 
                    use_direct_path = True
1664
 
                # the direct path can only be used if no other file has
1665
 
                # already taken this pathname, i.e. if the name is unused, or
1666
 
                # if it is already associated with this trans_id.
1667
 
                elif self._case_sensitive_target:
1668
 
                    if (self._limbo_children_names[parent].get(filename)
1669
 
                        in (trans_id, None)):
1670
 
                        use_direct_path = True
1671
 
                else:
1672
 
                    for l_filename, l_trans_id in viewitems(
1673
 
                            self._limbo_children_names[parent]):
1674
 
                        if l_trans_id == trans_id:
1675
 
                            continue
1676
 
                        if l_filename.lower() == filename.lower():
1677
 
                            break
1678
 
                    else:
1679
 
                        use_direct_path = True
1680
 
 
1681
 
        if not use_direct_path:
1682
 
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
1683
 
 
1684
 
        limbo_name = pathjoin(self._limbo_files[parent], filename)
1685
 
        self._limbo_children[parent].add(trans_id)
1686
 
        self._limbo_children_names[parent][filename] = trans_id
1687
 
        return limbo_name
1688
 
 
1689
 
 
1690
1410
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1691
1411
        """Apply all changes to the inventory and filesystem.
1692
1412
 
1701
1421
            calculating one.
1702
1422
        :param _mover: Supply an alternate FileMover, for testing
1703
1423
        """
1704
 
        for hook in MutableTree.hooks['pre_transform']:
1705
 
            hook(self._tree, self)
1706
1424
        if not no_conflicts:
1707
1425
            self._check_malformed()
1708
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1426
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1427
        try:
1709
1428
            if precomputed_delta is None:
1710
 
                child_pb.update(gettext('Apply phase'), 0, 2)
 
1429
                child_pb.update('Apply phase', 0, 2)
1711
1430
                inventory_delta = self._generate_inventory_delta()
1712
1431
                offset = 1
1713
1432
            else:
1718
1437
            else:
1719
1438
                mover = _mover
1720
1439
            try:
1721
 
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
 
1440
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
1722
1441
                self._apply_removals(mover)
1723
 
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
 
1442
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
1724
1443
                modified_paths = self._apply_insertions(mover)
1725
1444
            except:
1726
1445
                mover.rollback()
1727
1446
                raise
1728
1447
            else:
1729
1448
                mover.apply_deletions()
1730
 
        if self.final_file_id(self.root) is None:
1731
 
            inventory_delta = [e for e in inventory_delta if e[0] != '']
 
1449
        finally:
 
1450
            child_pb.finished()
1732
1451
        self._tree.apply_inventory_delta(inventory_delta)
1733
 
        self._apply_observed_sha1s()
1734
1452
        self._done = True
1735
1453
        self.finalize()
1736
1454
        return _TransformResults(modified_paths, self.rename_count)
1738
1456
    def _generate_inventory_delta(self):
1739
1457
        """Generate an inventory delta for the current transform."""
1740
1458
        inventory_delta = []
 
1459
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1741
1460
        new_paths = self._inventory_altered()
1742
1461
        total_entries = len(new_paths) + len(self._removed_id)
1743
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1462
        try:
1744
1463
            for num, trans_id in enumerate(self._removed_id):
1745
1464
                if (num % 10) == 0:
1746
 
                    child_pb.update(gettext('removing file'), num, total_entries)
 
1465
                    child_pb.update('removing file', num, total_entries)
1747
1466
                if trans_id == self._new_root:
1748
1467
                    file_id = self._tree.get_root_id()
1749
1468
                else:
1755
1474
                inventory_delta.append((path, None, file_id, None))
1756
1475
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1757
1476
                                     new_paths)
 
1477
            entries = self._tree.iter_entries_by_dir(
 
1478
                new_path_file_ids.values())
 
1479
            old_paths = dict((e.file_id, p) for p, e in entries)
1758
1480
            final_kinds = {}
1759
1481
            for num, (path, trans_id) in enumerate(new_paths):
1760
1482
                if (num % 10) == 0:
1761
 
                    child_pb.update(gettext('adding file'),
 
1483
                    child_pb.update('adding file',
1762
1484
                                    num + len(self._removed_id), total_entries)
1763
1485
                file_id = new_path_file_ids[trans_id]
1764
1486
                if file_id is None:
1765
1487
                    continue
1766
1488
                needs_entry = False
1767
 
                kind = self.final_kind(trans_id)
1768
 
                if kind is None:
1769
 
                    kind = self._tree.stored_kind(
1770
 
                            self._tree.id2path(file_id), file_id)
 
1489
                try:
 
1490
                    kind = self.final_kind(trans_id)
 
1491
                except NoSuchFile:
 
1492
                    kind = self._tree.stored_kind(file_id)
1771
1493
                parent_trans_id = self.final_parent(trans_id)
1772
1494
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1773
1495
                if parent_file_id is None:
1782
1504
                    new_entry = inventory.make_entry(kind,
1783
1505
                        self.final_name(trans_id),
1784
1506
                        parent_file_id, file_id)
1785
 
                try:
1786
 
                    old_path = self._tree.id2path(new_entry.file_id)
1787
 
                except errors.NoSuchId:
1788
 
                    old_path = None
 
1507
                old_path = old_paths.get(new_entry.file_id)
1789
1508
                new_executability = self._new_executability.get(trans_id)
1790
1509
                if new_executability is not None:
1791
1510
                    new_entry.executable = new_executability
1792
1511
                inventory_delta.append(
1793
1512
                    (old_path, path, new_entry.file_id, new_entry))
 
1513
        finally:
 
1514
            child_pb.finished()
1794
1515
        return inventory_delta
1795
1516
 
1796
1517
    def _apply_removals(self, mover):
1802
1523
 
1803
1524
        If inventory_delta is None, no inventory delta generation is performed.
1804
1525
        """
1805
 
        tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
1806
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
1807
 
            for num, (path, trans_id) in enumerate(tree_paths):
1808
 
                # do not attempt to move root into a subdirectory of itself.
1809
 
                if path == '':
1810
 
                    continue
1811
 
                child_pb.update(gettext('removing file'), num, len(tree_paths))
 
1526
        tree_paths = list(self._tree_path_ids.iteritems())
 
1527
        tree_paths.sort(reverse=True)
 
1528
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1529
        try:
 
1530
            for num, data in enumerate(tree_paths):
 
1531
                path, trans_id = data
 
1532
                child_pb.update('removing file', num, len(tree_paths))
1812
1533
                full_path = self._tree.abspath(path)
1813
1534
                if trans_id in self._removed_contents:
1814
 
                    delete_path = os.path.join(self._deletiondir, trans_id)
1815
 
                    mover.pre_delete(full_path, delete_path)
1816
 
                elif (trans_id in self._new_name
1817
 
                      or trans_id in self._new_parent):
 
1535
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
 
1536
                                     trans_id))
 
1537
                elif trans_id in self._new_name or trans_id in \
 
1538
                    self._new_parent:
1818
1539
                    try:
1819
1540
                        mover.rename(full_path, self._limbo_name(trans_id))
1820
 
                    except errors.TransformRenameFailed as e:
 
1541
                    except OSError, e:
1821
1542
                        if e.errno != errno.ENOENT:
1822
1543
                            raise
1823
1544
                    else:
1824
1545
                        self.rename_count += 1
 
1546
        finally:
 
1547
            child_pb.finished()
1825
1548
 
1826
1549
    def _apply_insertions(self, mover):
1827
1550
        """Perform tree operations that insert directory/inventory names.
1837
1560
        modified_paths = []
1838
1561
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1839
1562
                                 new_paths)
1840
 
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
1563
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1564
        try:
1841
1565
            for num, (path, trans_id) in enumerate(new_paths):
1842
1566
                if (num % 10) == 0:
1843
 
                    child_pb.update(gettext('adding file'), num, len(new_paths))
 
1567
                    child_pb.update('adding file', num, len(new_paths))
1844
1568
                full_path = self._tree.abspath(path)
1845
1569
                if trans_id in self._needs_rename:
1846
1570
                    try:
1847
1571
                        mover.rename(self._limbo_name(trans_id), full_path)
1848
 
                    except errors.TransformRenameFailed as e:
 
1572
                    except OSError, e:
1849
1573
                        # We may be renaming a dangling inventory id
1850
1574
                        if e.errno != errno.ENOENT:
1851
1575
                            raise
1852
1576
                    else:
1853
1577
                        self.rename_count += 1
1854
 
                    # TODO: if trans_id in self._observed_sha1s, we should
1855
 
                    #       re-stat the final target, since ctime will be
1856
 
                    #       updated by the change.
1857
1578
                if (trans_id in self._new_contents or
1858
1579
                    self.path_changed(trans_id)):
1859
1580
                    if trans_id in self._new_contents:
1860
1581
                        modified_paths.append(full_path)
1861
1582
                if trans_id in self._new_executability:
1862
1583
                    self._set_executability(path, trans_id)
1863
 
                if trans_id in self._observed_sha1s:
1864
 
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
1865
 
                    st = osutils.lstat(full_path)
1866
 
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1867
 
        for path, trans_id in new_paths:
1868
 
            # new_paths includes stuff like workingtree conflicts. Only the
1869
 
            # stuff in new_contents actually comes from limbo.
1870
 
            if trans_id in self._limbo_files:
1871
 
                del self._limbo_files[trans_id]
 
1584
        finally:
 
1585
            child_pb.finished()
1872
1586
        self._new_contents.clear()
1873
1587
        return modified_paths
1874
1588
 
1875
 
    def _apply_observed_sha1s(self):
1876
 
        """After we have finished renaming everything, update observed sha1s
1877
 
 
1878
 
        This has to be done after self._tree.apply_inventory_delta, otherwise
1879
 
        it doesn't know anything about the files we are updating. Also, we want
1880
 
        to do this as late as possible, so that most entries end up cached.
1881
 
        """
1882
 
        # TODO: this doesn't update the stat information for directories. So
1883
 
        #       the first 'bzr status' will still need to rewrite
1884
 
        #       .bzr/checkout/dirstate. However, we at least don't need to
1885
 
        #       re-read all of the files.
1886
 
        # TODO: If the operation took a while, we could do a time.sleep(3) here
1887
 
        #       to allow the clock to tick over and ensure we won't have any
1888
 
        #       problems. (we could observe start time, and finish time, and if
1889
 
        #       it is less than eg 10% overhead, add a sleep call.)
1890
 
        paths = FinalPaths(self)
1891
 
        for trans_id, observed in viewitems(self._observed_sha1s):
1892
 
            path = paths.get_path(trans_id)
1893
 
            # We could get the file_id, but dirstate prefers to use the path
1894
 
            # anyway, and it is 'cheaper' to determine.
1895
 
            # file_id = self._new_id[trans_id]
1896
 
            self._tree._observed_sha1(None, path, observed)
1897
 
 
1898
1589
 
1899
1590
class TransformPreview(DiskTreeTransform):
1900
1591
    """A TreeTransform for generating preview trees.
1904
1595
    unversioned files in the input tree.
1905
1596
    """
1906
1597
 
1907
 
    def __init__(self, tree, pb=None, case_sensitive=True):
 
1598
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1908
1599
        tree.lock_read()
1909
1600
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1910
1601
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1915
1606
    def tree_kind(self, trans_id):
1916
1607
        path = self._tree_id_paths.get(trans_id)
1917
1608
        if path is None:
1918
 
            return None
1919
 
        kind = self._tree.path_content_summary(path)[0]
1920
 
        if kind == 'missing':
1921
 
            kind = None
1922
 
        return kind
 
1609
            raise NoSuchFile(None)
 
1610
        file_id = self._tree.path2id(path)
 
1611
        return self._tree.kind(file_id)
1923
1612
 
1924
1613
    def _set_mode(self, trans_id, mode_id, typefunc):
1925
1614
        """Set the mode of new file contents.
1936
1625
            path = self._tree_id_paths[parent_id]
1937
1626
        except KeyError:
1938
1627
            return
1939
 
        entry = next(self._tree.iter_entries_by_dir(
1940
 
                specific_files=[path]))[1]
 
1628
        file_id = self.tree_file_id(parent_id)
 
1629
        if file_id is None:
 
1630
            return
 
1631
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1941
1632
        children = getattr(entry, 'children', {})
1942
1633
        for child in children:
1943
1634
            childpath = joinpath(path, child)
1944
1635
            yield self.trans_id_tree_path(childpath)
1945
1636
 
1946
 
    def new_orphan(self, trans_id, parent_id):
1947
 
        raise NotImplementedError(self.new_orphan)
1948
 
 
1949
 
 
1950
 
class _PreviewTree(inventorytree.InventoryTree):
 
1637
 
 
1638
class _PreviewTree(tree.Tree):
1951
1639
    """Partial implementation of Tree to support show_diff_trees"""
1952
1640
 
1953
1641
    def __init__(self, transform):
1981
1669
            except errors.NoSuchRevisionInTree:
1982
1670
                yield self._get_repository().revision_tree(revision_id)
1983
1671
 
1984
 
    def _get_file_revision(self, path, file_id, vf, tree_revision):
1985
 
        parent_keys = [
1986
 
                (file_id, t.get_file_revision(t.id2path(file_id), file_id))
1987
 
                for t in self._iter_parent_trees()]
 
1672
    def _get_file_revision(self, file_id, vf, tree_revision):
 
1673
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
1674
                       self._iter_parent_trees()]
1988
1675
        vf.add_lines((file_id, tree_revision), parent_keys,
1989
 
                     self.get_file_lines(path, file_id))
 
1676
                     self.get_file(file_id).readlines())
1990
1677
        repo = self._get_repository()
1991
1678
        base_vf = repo.texts
1992
1679
        if base_vf not in vf.fallback_versionedfiles:
1993
1680
            vf.fallback_versionedfiles.append(base_vf)
1994
1681
        return tree_revision
1995
1682
 
1996
 
    def _stat_limbo_file(self, trans_id):
 
1683
    def _stat_limbo_file(self, file_id):
 
1684
        trans_id = self._transform.trans_id_file_id(file_id)
1997
1685
        name = self._transform._limbo_name(trans_id)
1998
1686
        return os.lstat(name)
1999
1687
 
2010
1698
            executable = False
2011
1699
        else:
2012
1700
            file_id = self._transform.final_file_id(self._path2trans_id(path))
2013
 
            executable = self.is_executable(path, file_id)
 
1701
            executable = self.is_executable(file_id, path)
2014
1702
        return kind, executable, None
2015
1703
 
2016
 
    def is_locked(self):
2017
 
        return False
2018
 
 
2019
1704
    def lock_read(self):
2020
1705
        # Perhaps in theory, this should lock the TreeTransform?
2021
 
        return lock.LogicalLockResult(self.unlock)
 
1706
        pass
2022
1707
 
2023
1708
    def unlock(self):
2024
1709
        pass
2025
1710
 
2026
1711
    @property
2027
 
    def root_inventory(self):
 
1712
    def inventory(self):
2028
1713
        """This Tree does not use inventory as its backing data."""
2029
 
        raise NotImplementedError(_PreviewTree.root_inventory)
 
1714
        raise NotImplementedError(_PreviewTree.inventory)
2030
1715
 
2031
1716
    def get_root_id(self):
2032
1717
        return self._transform.final_file_id(self._transform.root)
2035
1720
        tree_ids = set(self._transform._tree.all_file_ids())
2036
1721
        tree_ids.difference_update(self._transform.tree_file_id(t)
2037
1722
                                   for t in self._transform._removed_id)
2038
 
        tree_ids.update(viewvalues(self._transform._new_id))
 
1723
        tree_ids.update(self._transform._new_id.values())
2039
1724
        return tree_ids
2040
1725
 
2041
 
    def all_versioned_paths(self):
2042
 
        return {self.id2path(fid) for fid in self.all_file_ids()}
 
1726
    def __iter__(self):
 
1727
        return iter(self.all_file_ids())
2043
1728
 
2044
1729
    def _has_id(self, file_id, fallback_check):
2045
1730
        if file_id in self._transform._r_new_id:
2046
1731
            return True
2047
 
        elif file_id in {self._transform.tree_file_id(trans_id) for
2048
 
            trans_id in self._transform._removed_id}:
 
1732
        elif file_id in set([self._transform.tree_file_id(trans_id) for
 
1733
            trans_id in self._transform._removed_id]):
2049
1734
            return False
2050
1735
        else:
2051
1736
            return fallback_check(file_id)
2079
1764
        return cur_parent
2080
1765
 
2081
1766
    def path2id(self, path):
2082
 
        if isinstance(path, list):
2083
 
            if path == []:
2084
 
                path = [""]
2085
 
            path = osutils.pathjoin(*path)
2086
1767
        return self._transform.final_file_id(self._path2trans_id(path))
2087
1768
 
2088
1769
    def id2path(self, file_id):
2098
1779
            return children
2099
1780
        children = set(self._transform.iter_tree_children(trans_id))
2100
1781
        # children in the _new_parent set are provided by _by_parent.
2101
 
        children.difference_update(self._transform._new_parent)
 
1782
        children.difference_update(self._transform._new_parent.keys())
2102
1783
        children.update(self._by_parent.get(trans_id, []))
2103
1784
        self._all_children_cache[trans_id] = children
2104
1785
        return children
2105
1786
 
2106
 
    def _iter_children(self, file_id):
 
1787
    def iter_children(self, file_id):
2107
1788
        trans_id = self._transform.trans_id_file_id(file_id)
2108
1789
        for child_trans_id in self._all_children(trans_id):
2109
1790
            yield self._transform.final_file_id(child_trans_id)
2117
1798
            if self._transform.final_file_id(trans_id) is None:
2118
1799
                yield self._final_paths._determine_path(trans_id)
2119
1800
 
2120
 
    def _make_inv_entries(self, ordered_entries, specific_files=None):
 
1801
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
1802
        yield_parents=False):
2121
1803
        for trans_id, parent_file_id in ordered_entries:
2122
1804
            file_id = self._transform.final_file_id(trans_id)
2123
1805
            if file_id is None:
2124
1806
                continue
2125
 
            if (specific_files is not None and
2126
 
                self._final_paths.get_path(trans_id) not in specific_files):
 
1807
            if (specific_file_ids is not None
 
1808
                and file_id not in specific_file_ids):
2127
1809
                continue
2128
 
            kind = self._transform.final_kind(trans_id)
2129
 
            if kind is None:
2130
 
                kind = self._transform._tree.stored_kind(
2131
 
                    self._transform._tree.id2path(file_id),
2132
 
                    file_id)
 
1810
            try:
 
1811
                kind = self._transform.final_kind(trans_id)
 
1812
            except NoSuchFile:
 
1813
                kind = self._transform._tree.stored_kind(file_id)
2133
1814
            new_entry = inventory.make_entry(
2134
1815
                kind,
2135
1816
                self._transform.final_name(trans_id),
2150
1831
                ordered_ids.append((trans_id, parent_file_id))
2151
1832
        return ordered_ids
2152
1833
 
2153
 
    def iter_child_entries(self, path, file_id=None):
2154
 
        trans_id = self._path2trans_id(path)
2155
 
        if trans_id is None:
2156
 
            raise errors.NoSuchFile(path)
2157
 
        todo = [(child_trans_id, trans_id) for child_trans_id in
2158
 
                self._all_children(trans_id)]
2159
 
        for entry, trans_id in self._make_inv_entries(todo):
2160
 
            yield entry
2161
 
 
2162
 
    def iter_entries_by_dir(self, specific_files=None):
 
1834
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2163
1835
        # This may not be a maximally efficient implementation, but it is
2164
1836
        # reasonably straightforward.  An implementation that grafts the
2165
1837
        # TreeTransform changes onto the tree's iter_entries_by_dir results
2167
1839
        # position.
2168
1840
        ordered_ids = self._list_files_by_dir()
2169
1841
        for entry, trans_id in self._make_inv_entries(ordered_ids,
2170
 
            specific_files):
2171
 
            yield self._final_paths.get_path(trans_id), entry
 
1842
            specific_file_ids, yield_parents=yield_parents):
 
1843
            yield unicode(self._final_paths.get_path(trans_id)), entry
2172
1844
 
2173
1845
    def _iter_entries_for_dir(self, dir_path):
2174
1846
        """Return path, entry for items in a directory without recursing down."""
 
1847
        dir_file_id = self.path2id(dir_path)
2175
1848
        ordered_ids = []
2176
 
        dir_trans_id = self._path2trans_id(dir_path)
2177
 
        dir_id = self._transform.final_file_id(dir_trans_id)
2178
 
        for child_trans_id in self._all_children(dir_trans_id):
2179
 
            ordered_ids.append((child_trans_id, dir_id))
2180
 
        path_entries = []
 
1849
        for file_id in self.iter_children(dir_file_id):
 
1850
            trans_id = self._transform.trans_id_file_id(file_id)
 
1851
            ordered_ids.append((trans_id, file_id))
2181
1852
        for entry, trans_id in self._make_inv_entries(ordered_ids):
2182
 
            path_entries.append((self._final_paths.get_path(trans_id), entry))
2183
 
        path_entries.sort()
2184
 
        return path_entries
 
1853
            yield unicode(self._final_paths.get_path(trans_id)), entry
2185
1854
 
2186
1855
    def list_files(self, include_root=False, from_dir=None, recursive=True):
2187
1856
        """See WorkingTree.list_files."""
2209
1878
            for path, entry in entries:
2210
1879
                yield path, 'V', entry.kind, entry.file_id, entry
2211
1880
 
2212
 
    def kind(self, path, file_id=None):
2213
 
        trans_id = self._path2trans_id(path)
2214
 
        if trans_id is None:
2215
 
            raise errors.NoSuchFile(path)
 
1881
    def kind(self, file_id):
 
1882
        trans_id = self._transform.trans_id_file_id(file_id)
2216
1883
        return self._transform.final_kind(trans_id)
2217
1884
 
2218
 
    def stored_kind(self, path, file_id=None):
2219
 
        trans_id = self._path2trans_id(path)
2220
 
        if trans_id is None:
2221
 
            raise errors.NoSuchFile(path)
 
1885
    def stored_kind(self, file_id):
 
1886
        trans_id = self._transform.trans_id_file_id(file_id)
2222
1887
        try:
2223
1888
            return self._transform._new_contents[trans_id]
2224
1889
        except KeyError:
2225
 
            return self._transform._tree.stored_kind(path, file_id)
 
1890
            return self._transform._tree.stored_kind(file_id)
2226
1891
 
2227
 
    def get_file_mtime(self, path, file_id=None):
 
1892
    def get_file_mtime(self, file_id, path=None):
2228
1893
        """See Tree.get_file_mtime"""
2229
 
        if file_id is None:
2230
 
            file_id = self.path2id(path)
2231
 
        if file_id is None:
2232
 
            raise errors.NoSuchFile(path)
2233
1894
        if not self._content_change(file_id):
2234
 
            return self._transform._tree.get_file_mtime(
2235
 
                    self._transform._tree.id2path(file_id), file_id)
2236
 
        trans_id = self._path2trans_id(path)
2237
 
        return self._stat_limbo_file(trans_id).st_mtime
2238
 
 
2239
 
    def get_file_size(self, path, file_id=None):
 
1895
            return self._transform._tree.get_file_mtime(file_id)
 
1896
        return self._stat_limbo_file(file_id).st_mtime
 
1897
 
 
1898
    def _file_size(self, entry, stat_value):
 
1899
        return self.get_file_size(entry.file_id)
 
1900
 
 
1901
    def get_file_size(self, file_id):
2240
1902
        """See Tree.get_file_size"""
2241
 
        trans_id = self._path2trans_id(path)
2242
 
        if trans_id is None:
2243
 
            raise errors.NoSuchFile(path)
2244
 
        kind = self._transform.final_kind(trans_id)
2245
 
        if kind != 'file':
2246
 
            return None
2247
 
        if trans_id in self._transform._new_contents:
2248
 
            return self._stat_limbo_file(trans_id).st_size
2249
 
        if self.kind(path, file_id) == 'file':
2250
 
            return self._transform._tree.get_file_size(path, file_id)
 
1903
        if self.kind(file_id) == 'file':
 
1904
            return self._transform._tree.get_file_size(file_id)
2251
1905
        else:
2252
1906
            return None
2253
1907
 
2254
 
    def get_file_verifier(self, path, file_id=None, stat_value=None):
2255
 
        trans_id = self._path2trans_id(path)
2256
 
        if trans_id is None:
2257
 
            raise errors.NoSuchFile(path)
2258
 
        kind = self._transform._new_contents.get(trans_id)
2259
 
        if kind is None:
2260
 
            return self._transform._tree.get_file_verifier(path, file_id)
2261
 
        if kind == 'file':
2262
 
            with self.get_file(path, file_id) as fileobj:
2263
 
                return ("SHA1", sha_file(fileobj))
2264
 
 
2265
 
    def get_file_sha1(self, path, file_id=None, stat_value=None):
2266
 
        trans_id = self._path2trans_id(path)
2267
 
        if trans_id is None:
2268
 
            raise errors.NoSuchFile(path)
2269
 
        kind = self._transform._new_contents.get(trans_id)
2270
 
        if kind is None:
2271
 
            return self._transform._tree.get_file_sha1(path, file_id)
2272
 
        if kind == 'file':
2273
 
            with self.get_file(path, file_id) as fileobj:
 
1908
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
1909
        trans_id = self._transform.trans_id_file_id(file_id)
 
1910
        kind = self._transform._new_contents.get(trans_id)
 
1911
        if kind is None:
 
1912
            return self._transform._tree.get_file_sha1(file_id)
 
1913
        if kind == 'file':
 
1914
            fileobj = self.get_file(file_id)
 
1915
            try:
2274
1916
                return sha_file(fileobj)
 
1917
            finally:
 
1918
                fileobj.close()
2275
1919
 
2276
 
    def is_executable(self, path, file_id=None):
2277
 
        trans_id = self._path2trans_id(path)
2278
 
        if trans_id is None:
 
1920
    def is_executable(self, file_id, path=None):
 
1921
        if file_id is None:
2279
1922
            return False
 
1923
        trans_id = self._transform.trans_id_file_id(file_id)
2280
1924
        try:
2281
1925
            return self._transform._new_executability[trans_id]
2282
1926
        except KeyError:
2283
1927
            try:
2284
 
                return self._transform._tree.is_executable(path, file_id)
2285
 
            except OSError as e:
 
1928
                return self._transform._tree.is_executable(file_id, path)
 
1929
            except OSError, e:
2286
1930
                if e.errno == errno.ENOENT:
2287
1931
                    return False
2288
1932
                raise
2289
 
            except errors.NoSuchFile:
 
1933
            except errors.NoSuchId:
2290
1934
                return False
2291
1935
 
2292
 
    def has_filename(self, path):
2293
 
        trans_id = self._path2trans_id(path)
2294
 
        if trans_id in self._transform._new_contents:
2295
 
            return True
2296
 
        elif trans_id in self._transform._removed_contents:
2297
 
            return False
2298
 
        else:
2299
 
            return self._transform._tree.has_filename(path)
2300
 
 
2301
1936
    def path_content_summary(self, path):
2302
1937
        trans_id = self._path2trans_id(path)
2303
1938
        tt = self._transform
2316
1951
            if kind == 'file':
2317
1952
                statval = os.lstat(limbo_name)
2318
1953
                size = statval.st_size
2319
 
                if not tt._limbo_supports_executable():
2320
 
                    executable = False
 
1954
                if not supports_executable():
 
1955
                    executable = None
2321
1956
                else:
2322
1957
                    executable = statval.st_mode & S_IEXEC
2323
1958
            else:
2325
1960
                executable = None
2326
1961
            if kind == 'symlink':
2327
1962
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2328
 
        executable = tt._new_executability.get(trans_id, executable)
 
1963
        if supports_executable():
 
1964
            executable = tt._new_executability.get(trans_id, executable)
2329
1965
        return kind, size, executable, link_or_sha1
2330
1966
 
2331
1967
    def iter_changes(self, from_tree, include_unchanged=False,
2349
1985
            raise ValueError('want_unversioned is not supported')
2350
1986
        return self._transform.iter_changes()
2351
1987
 
2352
 
    def get_file(self, path, file_id=None):
 
1988
    def get_file(self, file_id, path=None):
2353
1989
        """See Tree.get_file"""
2354
 
        if file_id is None:
2355
 
            file_id = self.path2id(path)
2356
1990
        if not self._content_change(file_id):
2357
 
            return self._transform._tree.get_file(path, file_id)
2358
 
        trans_id = self._path2trans_id(path)
 
1991
            return self._transform._tree.get_file(file_id, path)
 
1992
        trans_id = self._transform.trans_id_file_id(file_id)
2359
1993
        name = self._transform._limbo_name(trans_id)
2360
1994
        return open(name, 'rb')
2361
1995
 
2362
 
    def get_file_with_stat(self, path, file_id=None):
2363
 
        return self.get_file(path, file_id), None
 
1996
    def get_file_with_stat(self, file_id, path=None):
 
1997
        return self.get_file(file_id, path), None
2364
1998
 
2365
 
    def annotate_iter(self, path, file_id=None,
 
1999
    def annotate_iter(self, file_id,
2366
2000
                      default_revision=_mod_revision.CURRENT_REVISION):
2367
 
        if file_id is None:
2368
 
            file_id = self.path2id(path)
2369
2001
        changes = self._iter_changes_cache.get(file_id)
2370
2002
        if changes is None:
2371
2003
            get_old = True
2376
2008
                return None
2377
2009
            get_old = (kind[0] == 'file' and versioned[0])
2378
2010
        if get_old:
2379
 
            old_annotation = self._transform._tree.annotate_iter(
2380
 
                    path, file_id=file_id, default_revision=default_revision)
 
2011
            old_annotation = self._transform._tree.annotate_iter(file_id,
 
2012
                default_revision=default_revision)
2381
2013
        else:
2382
2014
            old_annotation = []
2383
2015
        if changes is None:
2392
2024
        #       It would be nice to be able to use the new Annotator based
2393
2025
        #       approach, as well.
2394
2026
        return annotate.reannotate([old_annotation],
2395
 
                                   self.get_file(path, file_id).readlines(),
 
2027
                                   self.get_file(file_id).readlines(),
2396
2028
                                   default_revision)
2397
2029
 
2398
 
    def get_symlink_target(self, path, file_id=None):
 
2030
    def get_symlink_target(self, file_id):
2399
2031
        """See Tree.get_symlink_target"""
2400
 
        if file_id is None:
2401
 
            file_id = self.path2id(path)
2402
2032
        if not self._content_change(file_id):
2403
 
            return self._transform._tree.get_symlink_target(path)
2404
 
        trans_id = self._path2trans_id(path)
 
2033
            return self._transform._tree.get_symlink_target(file_id)
 
2034
        trans_id = self._transform.trans_id_file_id(file_id)
2405
2035
        name = self._transform._limbo_name(trans_id)
2406
2036
        return osutils.readlink(name)
2407
2037
 
2418
2048
                path_from_root = self._final_paths.get_path(child_id)
2419
2049
                basename = self._transform.final_name(child_id)
2420
2050
                file_id = self._transform.final_file_id(child_id)
2421
 
                kind  = self._transform.final_kind(child_id)
2422
 
                if kind is not None:
 
2051
                try:
 
2052
                    kind = self._transform.final_kind(child_id)
2423
2053
                    versioned_kind = kind
2424
 
                else:
 
2054
                except NoSuchFile:
2425
2055
                    kind = 'unknown'
2426
 
                    versioned_kind = self._transform._tree.stored_kind(
2427
 
                            self._transform._tree.id2path(file_id),
2428
 
                            file_id)
 
2056
                    versioned_kind = self._transform._tree.stored_kind(file_id)
2429
2057
                if versioned_kind == 'directory':
2430
2058
                    subdirs.append(child_id)
2431
2059
                children.append((path_from_root, basename, kind, None,
2467
2095
 
2468
2096
    def _determine_path(self, trans_id):
2469
2097
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2470
 
            return u""
 
2098
            return ""
2471
2099
        name = self.transform.final_name(trans_id)
2472
2100
        parent_id = self.transform.final_parent(trans_id)
2473
2101
        if parent_id == self.transform.root:
2519
2147
    :param delta_from_tree: If true, build_tree may use the input Tree to
2520
2148
        generate the inventory delta.
2521
2149
    """
2522
 
    with wt.lock_tree_write(), tree.lock_read():
2523
 
        if accelerator_tree is not None:
2524
 
            accelerator_tree.lock_read()
 
2150
    wt.lock_tree_write()
 
2151
    try:
 
2152
        tree.lock_read()
2525
2153
        try:
2526
 
            return _build_tree(tree, wt, accelerator_tree, hardlink,
2527
 
                               delta_from_tree)
2528
 
        finally:
2529
2154
            if accelerator_tree is not None:
2530
 
                accelerator_tree.unlock()
 
2155
                accelerator_tree.lock_read()
 
2156
            try:
 
2157
                return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2158
                                   delta_from_tree)
 
2159
            finally:
 
2160
                if accelerator_tree is not None:
 
2161
                    accelerator_tree.unlock()
 
2162
        finally:
 
2163
            tree.unlock()
 
2164
    finally:
 
2165
        wt.unlock()
2531
2166
 
2532
2167
 
2533
2168
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2534
2169
    """See build_tree."""
2535
 
    for num, _unused in enumerate(wt.all_versioned_paths()):
 
2170
    for num, _unused in enumerate(wt.all_file_ids()):
2536
2171
        if num > 0:  # more than just a root
2537
2172
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
2173
    existing_files = set()
 
2174
    for dir, files in wt.walkdirs():
 
2175
        existing_files.update(f[0] for f in files)
2538
2176
    file_trans_id = {}
2539
 
    top_pb = ui.ui_factory.nested_progress_bar()
 
2177
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2540
2178
    pp = ProgressPhase("Build phase", 2, top_pb)
2541
 
    if tree.get_root_id() is not None:
 
2179
    if tree.inventory.root is not None:
2542
2180
        # This is kind of a hack: we should be altering the root
2543
2181
        # as part of the regular tree shape diff logic.
2544
2182
        # The conditional test here is to avoid doing an
2553
2191
    divert = set()
2554
2192
    try:
2555
2193
        pp.next_phase()
2556
 
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_path('')
2557
 
        with ui.ui_factory.nested_progress_bar() as pb:
 
2194
        file_trans_id[wt.get_root_id()] = \
 
2195
            tt.trans_id_tree_file_id(wt.get_root_id())
 
2196
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2197
        try:
2558
2198
            deferred_contents = []
2559
2199
            num = 0
2560
 
            total = len(tree.all_versioned_paths())
 
2200
            total = len(tree.inventory)
2561
2201
            if delta_from_tree:
2562
2202
                precomputed_delta = []
2563
2203
            else:
2564
2204
                precomputed_delta = None
2565
 
            # Check if tree inventory has content. If so, we populate
2566
 
            # existing_files with the directory content. If there are no
2567
 
            # entries we skip populating existing_files as its not used.
2568
 
            # This improves performance and unncessary work on large
2569
 
            # directory trees. (#501307)
2570
 
            if total > 0:
2571
 
                existing_files = set()
2572
 
                for dir, files in wt.walkdirs():
2573
 
                    existing_files.update(f[0] for f in files)
2574
2205
            for num, (tree_path, entry) in \
2575
 
                enumerate(tree.iter_entries_by_dir()):
2576
 
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
 
2206
                enumerate(tree.inventory.iter_entries_by_dir()):
 
2207
                pb.update("Building tree", num - len(deferred_contents), total)
2577
2208
                if entry.parent_id is None:
2578
2209
                    continue
2579
2210
                reparent = False
2585
2216
                    kind = file_kind(target_path)
2586
2217
                    if kind == "directory":
2587
2218
                        try:
2588
 
                            controldir.ControlDir.open(target_path)
 
2219
                            bzrdir.BzrDir.open(target_path)
2589
2220
                        except errors.NotBranchError:
2590
2221
                            pass
2591
2222
                        else:
2592
2223
                            divert.add(file_id)
2593
2224
                    if (file_id not in divert and
2594
 
                        _content_match(tree, entry, tree_path, file_id, kind,
 
2225
                        _content_match(tree, entry, file_id, kind,
2595
2226
                        target_path)):
2596
2227
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
2597
2228
                        if kind == 'directory':
2603
2234
                    trans_id = tt.create_path(entry.name, parent_id)
2604
2235
                    file_trans_id[file_id] = trans_id
2605
2236
                    tt.version_file(file_id, trans_id)
2606
 
                    executable = tree.is_executable(tree_path, file_id)
 
2237
                    executable = tree.is_executable(file_id, tree_path)
2607
2238
                    if executable:
2608
2239
                        tt.set_executability(executable, trans_id)
2609
 
                    trans_data = (trans_id, file_id, tree_path, entry.text_sha1)
2610
 
                    deferred_contents.append((tree_path, trans_data))
 
2240
                    trans_data = (trans_id, tree_path)
 
2241
                    deferred_contents.append((file_id, trans_data))
2611
2242
                else:
2612
 
                    file_trans_id[file_id] = new_by_entry(
2613
 
                            tree_path, tt, entry, parent_id, tree)
 
2243
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
2244
                                                          tree)
2614
2245
                if reparent:
2615
2246
                    new_trans_id = file_trans_id[file_id]
2616
2247
                    old_parent = tt.trans_id_tree_path(tree_path)
2618
2249
            offset = num + 1 - len(deferred_contents)
2619
2250
            _create_files(tt, tree, deferred_contents, pb, offset,
2620
2251
                          accelerator_tree, hardlink)
 
2252
        finally:
 
2253
            pb.finished()
2621
2254
        pp.next_phase()
2622
2255
        divert_trans = set(file_trans_id[f] for f in divert)
2623
2256
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
2626
2259
            precomputed_delta = None
2627
2260
        conflicts = cook_conflicts(raw_conflicts, tt)
2628
2261
        for conflict in conflicts:
2629
 
            trace.warning(text_type(conflict))
 
2262
            warning(conflict)
2630
2263
        try:
2631
2264
            wt.add_conflicts(conflicts)
2632
2265
        except errors.UnsupportedOperation:
2647
2280
        new_desired_files = desired_files
2648
2281
    else:
2649
2282
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2650
 
        unchanged = [(p[0], p[1]) for (f, p, c, v, d, n, k, e)
2651
 
                     in iter if not (c or e[0] != e[1])]
2652
 
        if accelerator_tree.supports_content_filtering():
2653
 
            unchanged = [(tp, ap) for (tp, ap) in unchanged
2654
 
                         if not next(accelerator_tree.iter_search_rules([ap]))]
2655
 
        unchanged = dict(unchanged)
 
2283
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
 
2284
                         in iter if not (c or e[0] != e[1]))
2656
2285
        new_desired_files = []
2657
2286
        count = 0
2658
 
        for unused_tree_path, (trans_id, file_id, tree_path, text_sha1) in desired_files:
2659
 
            accelerator_path = unchanged.get(tree_path)
 
2287
        for file_id, (trans_id, tree_path) in desired_files:
 
2288
            accelerator_path = unchanged.get(file_id)
2660
2289
            if accelerator_path is None:
2661
 
                new_desired_files.append((tree_path,
2662
 
                    (trans_id, file_id, tree_path, text_sha1)))
 
2290
                new_desired_files.append((file_id, (trans_id, tree_path)))
2663
2291
                continue
2664
 
            pb.update(gettext('Adding file contents'), count + offset, total)
 
2292
            pb.update('Adding file contents', count + offset, total)
2665
2293
            if hardlink:
2666
2294
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2667
2295
                                   trans_id)
2668
2296
            else:
2669
 
                with accelerator_tree.get_file(accelerator_path, file_id) as f:
2670
 
                    chunks = osutils.file_iterator(f)
2671
 
                    if wt.supports_content_filtering():
2672
 
                        filters = wt._content_filter_stack(tree_path)
2673
 
                        chunks = filtered_output_bytes(chunks, filters,
2674
 
                            ContentFilterContext(tree_path, tree))
2675
 
                    tt.create_file(chunks, trans_id, sha1=text_sha1)
 
2297
                contents = accelerator_tree.get_file(file_id, accelerator_path)
 
2298
                if wt.supports_content_filtering():
 
2299
                    filters = wt._content_filter_stack(tree_path)
 
2300
                    contents = filtered_output_bytes(contents, filters,
 
2301
                        ContentFilterContext(tree_path, tree))
 
2302
                try:
 
2303
                    tt.create_file(contents, trans_id)
 
2304
                finally:
 
2305
                    try:
 
2306
                        contents.close()
 
2307
                    except AttributeError:
 
2308
                        # after filtering, contents may no longer be file-like
 
2309
                        pass
2676
2310
            count += 1
2677
2311
        offset += count
2678
 
    for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
 
2312
    for count, ((trans_id, tree_path), contents) in enumerate(
2679
2313
            tree.iter_files_bytes(new_desired_files)):
2680
2314
        if wt.supports_content_filtering():
2681
2315
            filters = wt._content_filter_stack(tree_path)
2682
2316
            contents = filtered_output_bytes(contents, filters,
2683
2317
                ContentFilterContext(tree_path, tree))
2684
 
        tt.create_file(contents, trans_id, sha1=text_sha1)
2685
 
        pb.update(gettext('Adding file contents'), count + offset, total)
 
2318
        tt.create_file(contents, trans_id)
 
2319
        pb.update('Adding file contents', count + offset, total)
2686
2320
 
2687
2321
 
2688
2322
def _reparent_children(tt, old_parent, new_parent):
2689
2323
    for child in tt.iter_tree_children(old_parent):
2690
2324
        tt.adjust_path(tt.final_name(child), new_parent, child)
2691
2325
 
2692
 
 
2693
2326
def _reparent_transform_children(tt, old_parent, new_parent):
2694
2327
    by_parent = tt.by_parent()
2695
2328
    for child in by_parent[old_parent]:
2696
2329
        tt.adjust_path(tt.final_name(child), new_parent, child)
2697
2330
    return by_parent[old_parent]
2698
2331
 
2699
 
 
2700
 
def _content_match(tree, entry, tree_path, file_id, kind, target_path):
 
2332
def _content_match(tree, entry, file_id, kind, target_path):
2701
2333
    if entry.kind != kind:
2702
2334
        return False
2703
2335
    if entry.kind == "directory":
2704
2336
        return True
2705
2337
    if entry.kind == "file":
2706
 
        with open(target_path, 'rb') as f1, \
2707
 
             tree.get_file(tree_path, file_id) as f2:
2708
 
            if osutils.compare_files(f1, f2):
2709
 
                return True
 
2338
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
 
2339
            return True
2710
2340
    elif entry.kind == "symlink":
2711
 
        if tree.get_symlink_target(tree_path, file_id) == os.readlink(target_path):
 
2341
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
2712
2342
            return True
2713
2343
    return False
2714
2344
 
2743
2373
    return new_conflicts
2744
2374
 
2745
2375
 
2746
 
def new_by_entry(path, tt, entry, parent_id, tree):
 
2376
def new_by_entry(tt, entry, parent_id, tree):
2747
2377
    """Create a new file according to its inventory entry"""
2748
2378
    name = entry.name
2749
2379
    kind = entry.kind
2750
2380
    if kind == 'file':
2751
 
        with tree.get_file(path, entry.file_id) as f:
2752
 
            executable = tree.is_executable(path, entry.file_id)
2753
 
            return tt.new_file(
2754
 
                    name, parent_id, osutils.file_iterator(f), entry.file_id,
2755
 
                    executable)
 
2381
        contents = tree.get_file(entry.file_id).readlines()
 
2382
        executable = tree.is_executable(entry.file_id)
 
2383
        return tt.new_file(name, parent_id, contents, entry.file_id,
 
2384
                           executable)
2756
2385
    elif kind in ('directory', 'tree-reference'):
2757
2386
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
2758
2387
        if kind == 'tree-reference':
2759
2388
            tt.set_tree_reference(entry.reference_revision, trans_id)
2760
2389
        return trans_id
2761
2390
    elif kind == 'symlink':
2762
 
        target = tree.get_symlink_target(path, entry.file_id)
 
2391
        target = tree.get_symlink_target(entry.file_id)
2763
2392
        return tt.new_symlink(name, parent_id, target, entry.file_id)
2764
2393
    else:
2765
2394
        raise errors.BadFileKindError(name, kind)
2766
2395
 
2767
2396
 
2768
 
def create_from_tree(tt, trans_id, tree, path, file_id=None, chunks=None,
2769
 
    filter_tree_path=None):
2770
 
    """Create new file contents according to tree contents.
 
2397
@deprecated_function(deprecated_in((1, 9, 0)))
 
2398
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
 
2399
    """Create new file contents according to an inventory entry.
2771
2400
 
2772
 
    :param filter_tree_path: the tree path to use to lookup
2773
 
      content filters to apply to the bytes output in the working tree.
2774
 
      This only applies if the working tree supports content filtering.
 
2401
    DEPRECATED.  Use create_from_tree instead.
2775
2402
    """
2776
 
    kind = tree.kind(path, file_id)
 
2403
    if entry.kind == "file":
 
2404
        if lines is None:
 
2405
            lines = tree.get_file(entry.file_id).readlines()
 
2406
        tt.create_file(lines, trans_id, mode_id=mode_id)
 
2407
    elif entry.kind == "symlink":
 
2408
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
 
2409
    elif entry.kind == "directory":
 
2410
        tt.create_directory(trans_id)
 
2411
 
 
2412
 
 
2413
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
 
2414
    """Create new file contents according to tree contents."""
 
2415
    kind = tree.kind(file_id)
2777
2416
    if kind == 'directory':
2778
2417
        tt.create_directory(trans_id)
2779
2418
    elif kind == "file":
2780
 
        if chunks is None:
2781
 
            f = tree.get_file(path, file_id)
2782
 
            chunks = osutils.file_iterator(f)
2783
 
        else:
2784
 
            f = None
2785
 
        try:
2786
 
            wt = tt._tree
2787
 
            if wt.supports_content_filtering() and filter_tree_path is not None:
2788
 
                filters = wt._content_filter_stack(filter_tree_path)
2789
 
                chunks = filtered_output_bytes(chunks, filters,
2790
 
                    ContentFilterContext(filter_tree_path, tree))
2791
 
            tt.create_file(chunks, trans_id)
2792
 
        finally:
2793
 
            if f is not None:
2794
 
                f.close()
 
2419
        if bytes is None:
 
2420
            tree_file = tree.get_file(file_id)
 
2421
            try:
 
2422
                bytes = tree_file.readlines()
 
2423
            finally:
 
2424
                tree_file.close()
 
2425
        tt.create_file(bytes, trans_id)
2795
2426
    elif kind == "symlink":
2796
 
        tt.create_symlink(tree.get_symlink_target(path, file_id), trans_id)
 
2427
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2797
2428
    else:
2798
2429
        raise AssertionError('Unknown kind %r' % kind)
2799
2430
 
2804
2435
        tt.set_executability(entry.executable, trans_id)
2805
2436
 
2806
2437
 
 
2438
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
2439
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
2440
 
 
2441
 
 
2442
def _get_backup_name(name, by_parent, parent_trans_id, tt):
 
2443
    """Produce a backup-style name that appears to be available"""
 
2444
    def name_gen():
 
2445
        counter = 1
 
2446
        while True:
 
2447
            yield "%s.~%d~" % (name, counter)
 
2448
            counter += 1
 
2449
    for new_name in name_gen():
 
2450
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
2451
            return new_name
 
2452
 
 
2453
 
 
2454
def _entry_changes(file_id, entry, working_tree):
 
2455
    """Determine in which ways the inventory entry has changed.
 
2456
 
 
2457
    Returns booleans: has_contents, content_mod, meta_mod
 
2458
    has_contents means there are currently contents, but they differ
 
2459
    contents_mod means contents need to be modified
 
2460
    meta_mod means the metadata needs to be modified
 
2461
    """
 
2462
    cur_entry = working_tree.inventory[file_id]
 
2463
    try:
 
2464
        working_kind = working_tree.kind(file_id)
 
2465
        has_contents = True
 
2466
    except NoSuchFile:
 
2467
        has_contents = False
 
2468
        contents_mod = True
 
2469
        meta_mod = False
 
2470
    if has_contents is True:
 
2471
        if entry.kind != working_kind:
 
2472
            contents_mod, meta_mod = True, False
 
2473
        else:
 
2474
            cur_entry._read_tree_state(working_tree.id2path(file_id),
 
2475
                                       working_tree)
 
2476
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
 
2477
            cur_entry._forget_tree_state()
 
2478
    return has_contents, contents_mod, meta_mod
 
2479
 
 
2480
 
2807
2481
def revert(working_tree, target_tree, filenames, backups=False,
2808
 
           pb=None, change_reporter=None):
 
2482
           pb=DummyProgress(), change_reporter=None):
2809
2483
    """Revert a working tree's contents to those of a target tree."""
2810
 
    pb = ui.ui_factory.nested_progress_bar()
 
2484
    target_tree.lock_read()
 
2485
    tt = TreeTransform(working_tree, pb)
2811
2486
    try:
2812
 
        with target_tree.lock_read(), TreeTransform(working_tree, pb) as tt:
2813
 
            pp = ProgressPhase("Revert phase", 3, pb)
2814
 
            conflicts, merge_modified = _prepare_revert_transform(
2815
 
                working_tree, target_tree, tt, filenames, backups, pp)
2816
 
            if change_reporter:
2817
 
                change_reporter = delta._ChangeReporter(
2818
 
                    unversioned_filter=working_tree.is_ignored)
2819
 
                delta.report_changes(tt.iter_changes(), change_reporter)
2820
 
            for conflict in conflicts:
2821
 
                trace.warning(text_type(conflict))
2822
 
            pp.next_phase()
2823
 
            tt.apply()
2824
 
            if working_tree.supports_merge_modified():
2825
 
                working_tree.set_merge_modified(merge_modified)
 
2487
        pp = ProgressPhase("Revert phase", 3, pb)
 
2488
        conflicts, merge_modified = _prepare_revert_transform(
 
2489
            working_tree, target_tree, tt, filenames, backups, pp)
 
2490
        if change_reporter:
 
2491
            change_reporter = delta._ChangeReporter(
 
2492
                unversioned_filter=working_tree.is_ignored)
 
2493
            delta.report_changes(tt.iter_changes(), change_reporter)
 
2494
        for conflict in conflicts:
 
2495
            warning(conflict)
 
2496
        pp.next_phase()
 
2497
        tt.apply()
 
2498
        working_tree.set_merge_modified(merge_modified)
2826
2499
    finally:
 
2500
        target_tree.unlock()
 
2501
        tt.finalize()
2827
2502
        pb.clear()
2828
2503
    return conflicts
2829
2504
 
2831
2506
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2832
2507
                              backups, pp, basis_tree=None,
2833
2508
                              merge_modified=None):
2834
 
    with ui.ui_factory.nested_progress_bar() as child_pb:
 
2509
    pp.next_phase()
 
2510
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2511
    try:
2835
2512
        if merge_modified is None:
2836
2513
            merge_modified = working_tree.merge_modified()
2837
2514
        merge_modified = _alter_files(working_tree, target_tree, tt,
2838
2515
                                      child_pb, filenames, backups,
2839
2516
                                      merge_modified, basis_tree)
2840
 
    with ui.ui_factory.nested_progress_bar() as child_pb:
 
2517
    finally:
 
2518
        child_pb.finished()
 
2519
    pp.next_phase()
 
2520
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2521
    try:
2841
2522
        raw_conflicts = resolve_conflicts(tt, child_pb,
2842
2523
            lambda t, c: conflict_pass(t, c, target_tree))
 
2524
    finally:
 
2525
        child_pb.finished()
2843
2526
    conflicts = cook_conflicts(raw_conflicts, tt)
2844
2527
    return conflicts, merge_modified
2845
2528
 
2848
2531
                 backups, merge_modified, basis_tree=None):
2849
2532
    if basis_tree is not None:
2850
2533
        basis_tree.lock_read()
2851
 
    # We ask the working_tree for its changes relative to the target, rather
2852
 
    # than the target changes relative to the working tree. Because WT4 has an
2853
 
    # optimizer to compare itself to a target, but no optimizer for the
2854
 
    # reverse.
2855
 
    change_list = working_tree.iter_changes(target_tree,
 
2534
    change_list = target_tree.iter_changes(working_tree,
2856
2535
        specific_files=specific_files, pb=pb)
2857
 
    if not target_tree.is_versioned(u''):
 
2536
    if target_tree.get_root_id() is None:
2858
2537
        skip_root = True
2859
2538
    else:
2860
2539
        skip_root = False
2862
2541
        deferred_files = []
2863
2542
        for id_num, (file_id, path, changed_content, versioned, parent, name,
2864
2543
                kind, executable) in enumerate(change_list):
2865
 
            target_path, wt_path = path
2866
 
            target_versioned, wt_versioned = versioned
2867
 
            target_parent, wt_parent = parent
2868
 
            target_name, wt_name = name
2869
 
            target_kind, wt_kind = kind
2870
 
            target_executable, wt_executable = executable
2871
 
            if skip_root and wt_parent is None:
 
2544
            if skip_root and file_id[0] is not None and parent[0] is None:
2872
2545
                continue
2873
2546
            trans_id = tt.trans_id_file_id(file_id)
2874
2547
            mode_id = None
2875
2548
            if changed_content:
2876
2549
                keep_content = False
2877
 
                if wt_kind == 'file' and (backups or target_kind is None):
2878
 
                    wt_sha1 = working_tree.get_file_sha1(wt_path, file_id)
 
2550
                if kind[0] == 'file' and (backups or kind[1] is None):
 
2551
                    wt_sha1 = working_tree.get_file_sha1(file_id)
2879
2552
                    if merge_modified.get(file_id) != wt_sha1:
2880
2553
                        # acquire the basis tree lazily to prevent the
2881
2554
                        # expense of accessing it when it's not needed ?
2883
2556
                        if basis_tree is None:
2884
2557
                            basis_tree = working_tree.basis_tree()
2885
2558
                            basis_tree.lock_read()
2886
 
                        basis_path = find_previous_path(working_tree, basis_tree, wt_path)
2887
 
                        if basis_path is None:
2888
 
                            if target_kind is None and not target_versioned:
2889
 
                                keep_content = True
2890
 
                        else:
2891
 
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path, file_id):
2892
 
                                keep_content = True
2893
 
                if wt_kind is not None:
 
2559
                        if file_id in basis_tree:
 
2560
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
2561
                                keep_content = True
 
2562
                        elif kind[1] is None and not versioned[1]:
 
2563
                            keep_content = True
 
2564
                if kind[0] is not None:
2894
2565
                    if not keep_content:
2895
2566
                        tt.delete_contents(trans_id)
2896
 
                    elif target_kind is not None:
2897
 
                        parent_trans_id = tt.trans_id_file_id(wt_parent)
2898
 
                        backup_name = tt._available_backup_name(
2899
 
                            wt_name, parent_trans_id)
 
2567
                    elif kind[1] is not None:
 
2568
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
2569
                        by_parent = tt.by_parent()
 
2570
                        backup_name = _get_backup_name(name[0], by_parent,
 
2571
                                                       parent_trans_id, tt)
2900
2572
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
2901
 
                        new_trans_id = tt.create_path(wt_name, parent_trans_id)
2902
 
                        if wt_versioned and target_versioned:
 
2573
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
2574
                        if versioned == (True, True):
2903
2575
                            tt.unversion_file(trans_id)
2904
2576
                            tt.version_file(file_id, new_trans_id)
2905
2577
                        # New contents should have the same unix perms as old
2906
2578
                        # contents
2907
2579
                        mode_id = trans_id
2908
2580
                        trans_id = new_trans_id
2909
 
                if target_kind in ('directory', 'tree-reference'):
 
2581
                if kind[1] in ('directory', 'tree-reference'):
2910
2582
                    tt.create_directory(trans_id)
2911
 
                    if target_kind == 'tree-reference':
2912
 
                        revision = target_tree.get_reference_revision(
2913
 
                                target_path, file_id)
 
2583
                    if kind[1] == 'tree-reference':
 
2584
                        revision = target_tree.get_reference_revision(file_id,
 
2585
                                                                      path[1])
2914
2586
                        tt.set_tree_reference(revision, trans_id)
2915
 
                elif target_kind == 'symlink':
2916
 
                    tt.create_symlink(target_tree.get_symlink_target(
2917
 
                            target_path, file_id), trans_id)
2918
 
                elif target_kind == 'file':
2919
 
                    deferred_files.append((target_path, (trans_id, mode_id, file_id)))
 
2587
                elif kind[1] == 'symlink':
 
2588
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
2589
                                      trans_id)
 
2590
                elif kind[1] == 'file':
 
2591
                    deferred_files.append((file_id, (trans_id, mode_id)))
2920
2592
                    if basis_tree is None:
2921
2593
                        basis_tree = working_tree.basis_tree()
2922
2594
                        basis_tree.lock_read()
2923
 
                    new_sha1 = target_tree.get_file_sha1(target_path, file_id)
2924
 
                    basis_path = find_previous_path(target_tree, basis_tree, target_path)
2925
 
                    if (basis_path is not None and
2926
 
                        new_sha1 == basis_tree.get_file_sha1(basis_path, file_id)):
 
2595
                    new_sha1 = target_tree.get_file_sha1(file_id)
 
2596
                    if (file_id in basis_tree and new_sha1 ==
 
2597
                        basis_tree.get_file_sha1(file_id)):
2927
2598
                        if file_id in merge_modified:
2928
2599
                            del merge_modified[file_id]
2929
2600
                    else:
2930
2601
                        merge_modified[file_id] = new_sha1
2931
2602
 
2932
2603
                    # preserve the execute bit when backing up
2933
 
                    if keep_content and wt_executable == target_executable:
2934
 
                        tt.set_executability(target_executable, trans_id)
2935
 
                elif target_kind is not None:
2936
 
                    raise AssertionError(target_kind)
2937
 
            if not wt_versioned and target_versioned:
 
2604
                    if keep_content and executable[0] == executable[1]:
 
2605
                        tt.set_executability(executable[1], trans_id)
 
2606
                elif kind[1] is not None:
 
2607
                    raise AssertionError(kind[1])
 
2608
            if versioned == (False, True):
2938
2609
                tt.version_file(file_id, trans_id)
2939
 
            if wt_versioned and not target_versioned:
 
2610
            if versioned == (True, False):
2940
2611
                tt.unversion_file(trans_id)
2941
 
            if (target_name is not None and
2942
 
                (wt_name != target_name or wt_parent != target_parent)):
2943
 
                if target_name == '' and target_parent is None:
 
2612
            if (name[1] is not None and
 
2613
                (name[0] != name[1] or parent[0] != parent[1])):
 
2614
                if name[1] == '' and parent[1] is None:
2944
2615
                    parent_trans = ROOT_PARENT
2945
2616
                else:
2946
 
                    parent_trans = tt.trans_id_file_id(target_parent)
2947
 
                if wt_parent is None and wt_versioned:
2948
 
                    tt.adjust_root_path(target_name, parent_trans)
2949
 
                else:
2950
 
                    tt.adjust_path(target_name, parent_trans, trans_id)
2951
 
            if wt_executable != target_executable and target_kind == "file":
2952
 
                tt.set_executability(target_executable, trans_id)
2953
 
        if working_tree.supports_content_filtering():
2954
 
            for (trans_id, mode_id, file_id), bytes in (
2955
 
                target_tree.iter_files_bytes(deferred_files)):
2956
 
                # We're reverting a tree to the target tree so using the
2957
 
                # target tree to find the file path seems the best choice
2958
 
                # here IMO - Ian C 27/Oct/2009
2959
 
                filter_tree_path = target_tree.id2path(file_id)
2960
 
                filters = working_tree._content_filter_stack(filter_tree_path)
2961
 
                bytes = filtered_output_bytes(bytes, filters,
2962
 
                    ContentFilterContext(filter_tree_path, working_tree))
2963
 
                tt.create_file(bytes, trans_id, mode_id)
2964
 
        else:
2965
 
            for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
2966
 
                deferred_files):
2967
 
                tt.create_file(bytes, trans_id, mode_id)
2968
 
        tt.fixup_new_roots()
 
2617
                    parent_trans = tt.trans_id_file_id(parent[1])
 
2618
                tt.adjust_path(name[1], parent_trans, trans_id)
 
2619
            if executable[0] != executable[1] and kind[1] == "file":
 
2620
                tt.set_executability(executable[1], trans_id)
 
2621
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2622
            deferred_files):
 
2623
            tt.create_file(bytes, trans_id, mode_id)
2969
2624
    finally:
2970
2625
        if basis_tree is not None:
2971
2626
            basis_tree.unlock()
2972
2627
    return merge_modified
2973
2628
 
2974
2629
 
2975
 
def resolve_conflicts(tt, pb=None, pass_func=None):
 
2630
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
2976
2631
    """Make many conflict-resolution attempts, but die if they fail"""
2977
2632
    if pass_func is None:
2978
2633
        pass_func = conflict_pass
2979
2634
    new_conflicts = set()
2980
 
    with ui.ui_factory.nested_progress_bar() as pb:
 
2635
    try:
2981
2636
        for n in range(10):
2982
 
            pb.update(gettext('Resolution pass'), n+1, 10)
 
2637
            pb.update('Resolution pass', n+1, 10)
2983
2638
            conflicts = tt.find_conflicts()
2984
2639
            if len(conflicts) == 0:
2985
2640
                return new_conflicts
2986
2641
            new_conflicts.update(pass_func(tt, conflicts))
2987
2642
        raise MalformedTransform(conflicts=conflicts)
 
2643
    finally:
 
2644
        pb.clear()
2988
2645
 
2989
2646
 
2990
2647
def conflict_pass(tt, conflicts, path_tree=None):
3007
2664
                existing_file, new_file = conflict[2], conflict[1]
3008
2665
            else:
3009
2666
                existing_file, new_file = conflict[1], conflict[2]
3010
 
            new_name = tt.final_name(existing_file) + '.moved'
 
2667
            new_name = tt.final_name(existing_file)+'.moved'
3011
2668
            tt.adjust_path(new_name, final_parent, existing_file)
3012
2669
            new_conflicts.add((c_type, 'Moved existing file to',
3013
2670
                               existing_file, new_file))
3022
2679
 
3023
2680
        elif c_type == 'missing parent':
3024
2681
            trans_id = conflict[1]
3025
 
            if trans_id in tt._removed_contents:
3026
 
                cancel_deletion = True
3027
 
                orphans = tt._get_potential_orphans(trans_id)
3028
 
                if orphans:
3029
 
                    cancel_deletion = False
3030
 
                    # All children are orphans
3031
 
                    for o in orphans:
3032
 
                        try:
3033
 
                            tt.new_orphan(o, trans_id)
3034
 
                        except OrphaningError:
3035
 
                            # Something bad happened so we cancel the directory
3036
 
                            # deletion which will leave it in place with a
3037
 
                            # conflict. The user can deal with it from there.
3038
 
                            # Note that this also catch the case where we don't
3039
 
                            # want to create orphans and leave the directory in
3040
 
                            # place.
3041
 
                            cancel_deletion = True
3042
 
                            break
3043
 
                if cancel_deletion:
3044
 
                    # Cancel the directory deletion
3045
 
                    tt.cancel_deletion(trans_id)
3046
 
                    new_conflicts.add(('deleting parent', 'Not deleting',
3047
 
                                       trans_id))
3048
 
            else:
 
2682
            try:
 
2683
                tt.cancel_deletion(trans_id)
 
2684
                new_conflicts.add(('deleting parent', 'Not deleting',
 
2685
                                   trans_id))
 
2686
            except KeyError:
3049
2687
                create = True
3050
2688
                try:
3051
2689
                    tt.final_name(trans_id)
3054
2692
                        file_id = tt.final_file_id(trans_id)
3055
2693
                        if file_id is None:
3056
2694
                            file_id = tt.inactive_file_id(trans_id)
3057
 
                        _, entry = next(path_tree.iter_entries_by_dir(
3058
 
                            specific_files=[path_tree.id2path(file_id)]))
 
2695
                        entry = path_tree.inventory[file_id]
3059
2696
                        # special-case the other tree root (move its
3060
2697
                        # children to current root)
3061
2698
                        if entry.parent_id is None:
3062
 
                            create = False
 
2699
                            create=False
3063
2700
                            moved = _reparent_transform_children(
3064
2701
                                tt, trans_id, tt.root)
3065
2702
                            for child in moved:
3076
2713
        elif c_type == 'unversioned parent':
3077
2714
            file_id = tt.inactive_file_id(conflict[1])
3078
2715
            # special-case the other tree root (move its children instead)
3079
 
            if path_tree and path_tree.path2id('') == file_id:
3080
 
                    # This is the root entry, skip it
 
2716
            if path_tree and file_id in path_tree:
 
2717
                if path_tree.inventory[file_id].parent_id is None:
3081
2718
                    continue
3082
2719
            tt.version_file(file_id, conflict[1])
3083
2720
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
3099
2736
 
3100
2737
def cook_conflicts(raw_conflicts, tt):
3101
2738
    """Generate a list of cooked conflicts, sorted by file path"""
 
2739
    from bzrlib.conflicts import Conflict
3102
2740
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3103
 
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
 
2741
    return sorted(conflict_iter, key=Conflict.sort_key)
3104
2742
 
3105
2743
 
3106
2744
def iter_cook_conflicts(raw_conflicts, tt):
 
2745
    from bzrlib.conflicts import Conflict
3107
2746
    fp = FinalPaths(tt)
3108
2747
    for conflict in raw_conflicts:
3109
2748
        c_type = conflict[0]
3111
2750
        modified_path = fp.get_path(conflict[2])
3112
2751
        modified_id = tt.final_file_id(conflict[2])
3113
2752
        if len(conflict) == 3:
3114
 
            yield conflicts.Conflict.factory(
3115
 
                c_type, action=action, path=modified_path, file_id=modified_id)
 
2753
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
2754
                                     file_id=modified_id)
3116
2755
 
3117
2756
        else:
3118
2757
            conflicting_path = fp.get_path(conflict[3])
3119
2758
            conflicting_id = tt.final_file_id(conflict[3])
3120
 
            yield conflicts.Conflict.factory(
3121
 
                c_type, action=action, path=modified_path,
3122
 
                file_id=modified_id,
3123
 
                conflict_path=conflicting_path,
3124
 
                conflict_file_id=conflicting_id)
 
2759
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
2760
                                   file_id=modified_id,
 
2761
                                   conflict_path=conflicting_path,
 
2762
                                   conflict_file_id=conflicting_id)
3125
2763
 
3126
2764
 
3127
2765
class _FileMover(object):
3132
2770
        self.pending_deletions = []
3133
2771
 
3134
2772
    def rename(self, from_, to):
3135
 
        """Rename a file from one path to another."""
 
2773
        """Rename a file from one path to another.  Functions like os.rename"""
3136
2774
        try:
3137
2775
            os.rename(from_, to)
3138
 
        except OSError as e:
 
2776
        except OSError, e:
3139
2777
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
3140
2778
                raise errors.FileExists(to, str(e))
3141
 
            # normal OSError doesn't include filenames so it's hard to see where
3142
 
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
3143
 
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
 
2779
            raise
3144
2780
        self.past_renames.append((from_, to))
3145
2781
 
3146
2782
    def pre_delete(self, from_, to):
3156
2792
    def rollback(self):
3157
2793
        """Reverse all renames that have been performed"""
3158
2794
        for from_, to in reversed(self.past_renames):
3159
 
            try:
3160
 
                os.rename(to, from_)
3161
 
            except OSError as e:
3162
 
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
 
2795
            os.rename(to, from_)
3163
2796
        # after rollback, don't reuse _FileMover
3164
2797
        past_renames = None
3165
2798
        pending_deletions = None
3171
2804
        # after apply_deletions, don't reuse _FileMover
3172
2805
        past_renames = None
3173
2806
        pending_deletions = None
3174
 
 
3175
 
 
3176
 
def link_tree(target_tree, source_tree):
3177
 
    """Where possible, hard-link files in a tree to those in another tree.
3178
 
 
3179
 
    :param target_tree: Tree to change
3180
 
    :param source_tree: Tree to hard-link from
3181
 
    """
3182
 
    tt = TreeTransform(target_tree)
3183
 
    try:
3184
 
        for (file_id, paths, changed_content, versioned, parent, name, kind,
3185
 
             executable) in target_tree.iter_changes(source_tree,
3186
 
             include_unchanged=True):
3187
 
            if changed_content:
3188
 
                continue
3189
 
            if kind != ('file', 'file'):
3190
 
                continue
3191
 
            if executable[0] != executable[1]:
3192
 
                continue
3193
 
            trans_id = tt.trans_id_tree_path(paths[1])
3194
 
            tt.delete_contents(trans_id)
3195
 
            tt.create_hardlink(source_tree.abspath(paths[0]), trans_id)
3196
 
        tt.apply()
3197
 
    finally:
3198
 
        tt.finalize()