~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

(gz) Remove bzrlib/util/elementtree/ package (Martin Packman)

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
import os
18
20
import errno
19
21
from stat import S_ISREG, S_IEXEC
30
32
from bzrlib import (
31
33
    annotate,
32
34
    bencode,
33
 
    bzrdir,
 
35
    controldir,
34
36
    commit,
 
37
    conflicts,
35
38
    delta,
36
 
    errors,
37
39
    inventory,
38
40
    multiparent,
39
41
    osutils,
41
43
    ui,
42
44
    urlutils,
43
45
    )
 
46
from bzrlib.i18n import gettext
44
47
""")
45
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
48
from bzrlib.errors import (DuplicateKey, MalformedTransform,
46
49
                           ReusingTransform, CantMoveRoot,
47
50
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
48
51
                           UnableCreateSymlink)
137
140
        # A counter of how many files have been renamed
138
141
        self.rename_count = 0
139
142
 
 
143
    def __enter__(self):
 
144
        """Support Context Manager API."""
 
145
        return self
 
146
 
 
147
    def __exit__(self, exc_type, exc_val, exc_tb):
 
148
        """Support Context Manager API."""
 
149
        self.finalize()
 
150
 
140
151
    def finalize(self):
141
152
        """Release the working tree lock, if held.
142
153
 
217
228
        This means that the old root trans-id becomes obsolete, so it is
218
229
        recommended only to invoke this after the root trans-id has become
219
230
        irrelevant.
 
231
 
220
232
        """
221
233
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
222
234
                     ROOT_PARENT]
228
240
            self._new_root = new_roots[0]
229
241
            return
230
242
        old_new_root = new_roots[0]
231
 
        # TODO: What to do if a old_new_root is present, but self._new_root is
232
 
        #       not listed as being removed? This code explicitly unversions
233
 
        #       the old root and versions it with the new file_id. Though that
234
 
        #       seems like an incomplete delta
235
 
 
236
243
        # unversion the new root's directory.
237
 
        file_id = self.final_file_id(old_new_root)
 
244
        if self.final_kind(self._new_root) is None:
 
245
            file_id = self.final_file_id(old_new_root)
 
246
        else:
 
247
            file_id = self.final_file_id(self._new_root)
238
248
        if old_new_root in self._new_id:
239
249
            self.cancel_versioning(old_new_root)
240
250
        else:
244
254
        if (self.tree_file_id(self._new_root) is not None and
245
255
            self._new_root not in self._removed_id):
246
256
            self.unversion_file(self._new_root)
247
 
        self.version_file(file_id, self._new_root)
 
257
        if file_id is not None:
 
258
            self.version_file(file_id, self._new_root)
248
259
 
249
260
        # Now move children of new root into old root directory.
250
261
        # Ensure all children are registered with the transaction, but don't
384
395
        return sorted(FinalPaths(self).get_paths(new_ids))
385
396
 
386
397
    def _inventory_altered(self):
387
 
        """Get the trans_ids and paths of files needing new inv entries."""
388
 
        new_ids = set()
389
 
        for id_set in [self._new_name, self._new_parent, self._new_id,
 
398
        """Determine which trans_ids need new Inventory entries.
 
399
 
 
400
        An new entry is needed when anything that would be reflected by an
 
401
        inventory entry changes, including file name, file_id, parent file_id,
 
402
        file kind, and the execute bit.
 
403
 
 
404
        Some care is taken to return entries with real changes, not cases
 
405
        where the value is deleted and then restored to its original value,
 
406
        but some actually unchanged values may be returned.
 
407
 
 
408
        :returns: A list of (path, trans_id) for all items requiring an
 
409
            inventory change. Ordered by path.
 
410
        """
 
411
        changed_ids = set()
 
412
        # Find entries whose file_ids are new (or changed).
 
413
        new_file_id = set(t for t in self._new_id
 
414
                          if self._new_id[t] != self.tree_file_id(t))
 
415
        for id_set in [self._new_name, self._new_parent, new_file_id,
390
416
                       self._new_executability]:
391
 
            new_ids.update(id_set)
 
417
            changed_ids.update(id_set)
 
418
        # removing implies a kind change
392
419
        changed_kind = set(self._removed_contents)
 
420
        # so does adding
393
421
        changed_kind.intersection_update(self._new_contents)
394
 
        changed_kind.difference_update(new_ids)
 
422
        # Ignore entries that are already known to have changed.
 
423
        changed_kind.difference_update(changed_ids)
 
424
        #  to keep only the truly changed ones
395
425
        changed_kind = (t for t in changed_kind
396
426
                        if self.tree_kind(t) != self.final_kind(t))
397
 
        new_ids.update(changed_kind)
398
 
        return sorted(FinalPaths(self).get_paths(new_ids))
 
427
        # all kind changes will alter the inventory
 
428
        changed_ids.update(changed_kind)
 
429
        # To find entries with changed parent_ids, find parents which existed,
 
430
        # but changed file_id.
 
431
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
 
432
        # Now add all their children to the set.
 
433
        for parent_trans_id in new_file_id:
 
434
            changed_ids.update(self.iter_tree_children(parent_trans_id))
 
435
        return sorted(FinalPaths(self).get_paths(changed_ids))
399
436
 
400
437
    def final_kind(self, trans_id):
401
438
        """Determine the final file kind, after any changes applied.
526
563
        for trans_id in self._removed_id:
527
564
            file_id = self.tree_file_id(trans_id)
528
565
            if file_id is not None:
529
 
                # XXX: This seems like something that should go via a different
530
 
                #      indirection.
531
 
                if self._tree.inventory[file_id].kind == 'directory':
 
566
                if self._tree.stored_kind(file_id) == 'directory':
532
567
                    parents.append(trans_id)
533
568
            elif self.tree_kind(trans_id) == 'directory':
534
569
                parents.append(trans_id)
728
763
 
729
764
    def _set_executability(self, path, trans_id):
730
765
        """Set the executability of versioned files """
731
 
        if supports_executable():
 
766
        if self._tree._supports_executable():
732
767
            new_executability = self._new_executability[trans_id]
733
768
            abspath = self._tree.abspath(path)
734
769
            current_mode = os.stat(abspath).st_mode
743
778
                    to_mode |= 0010 & ~umask
744
779
            else:
745
780
                to_mode = current_mode & ~0111
746
 
            os.chmod(abspath, to_mode)
 
781
            osutils.chmod_if_possible(abspath, to_mode)
747
782
 
748
783
    def _new_entry(self, name, parent_id, file_id):
749
784
        """Helper function to create a new filesystem entry."""
1154
1189
        self._deletiondir = None
1155
1190
        # A mapping of transform ids to their limbo filename
1156
1191
        self._limbo_files = {}
 
1192
        self._possibly_stale_limbo_files = set()
1157
1193
        # A mapping of transform ids to a set of the transform ids of children
1158
1194
        # that their limbo directory has
1159
1195
        self._limbo_children = {}
1172
1208
        if self._tree is None:
1173
1209
            return
1174
1210
        try:
1175
 
            limbo_paths = sorted(self._limbo_files.values(), reverse=True)
 
1211
            limbo_paths = self._limbo_files.values() + list(
 
1212
                self._possibly_stale_limbo_files)
 
1213
            limbo_paths = sorted(limbo_paths, reverse=True)
1176
1214
            for path in limbo_paths:
1177
1215
                try:
1178
1216
                    delete_any(path)
1195
1233
        finally:
1196
1234
            TreeTransformBase.finalize(self)
1197
1235
 
 
1236
    def _limbo_supports_executable(self):
 
1237
        """Check if the limbo path supports the executable bit."""
 
1238
        # FIXME: Check actual file system capabilities of limbodir
 
1239
        return osutils.supports_executable()
 
1240
 
1198
1241
    def _limbo_name(self, trans_id):
1199
1242
        """Generate the limbo name of a file"""
1200
1243
        limbo_name = self._limbo_files.get(trans_id)
1236
1279
        entries from _limbo_files, because they are now stale.
1237
1280
        """
1238
1281
        for trans_id in trans_ids:
1239
 
            old_path = self._limbo_files.pop(trans_id)
 
1282
            old_path = self._limbo_files[trans_id]
 
1283
            self._possibly_stale_limbo_files.add(old_path)
 
1284
            del self._limbo_files[trans_id]
1240
1285
            if trans_id not in self._new_contents:
1241
1286
                continue
1242
1287
            new_path = self._limbo_name(trans_id)
1243
1288
            os.rename(old_path, new_path)
 
1289
            self._possibly_stale_limbo_files.remove(old_path)
1244
1290
            for descendant in self._limbo_descendants(trans_id):
1245
1291
                desc_path = self._limbo_files[descendant]
1246
1292
                desc_path = new_path + desc_path[len(old_path):]
1516
1562
        try:
1517
1563
            limbodir = urlutils.local_path_from_url(
1518
1564
                tree._transport.abspath('limbo'))
1519
 
            try:
1520
 
                os.mkdir(limbodir)
1521
 
            except OSError, e:
1522
 
                if e.errno == errno.EEXIST:
1523
 
                    raise ExistingLimbo(limbodir)
 
1565
            osutils.ensure_empty_directory_exists(
 
1566
                limbodir,
 
1567
                errors.ExistingLimbo)
1524
1568
            deletiondir = urlutils.local_path_from_url(
1525
1569
                tree._transport.abspath('pending-deletion'))
1526
 
            try:
1527
 
                os.mkdir(deletiondir)
1528
 
            except OSError, e:
1529
 
                if e.errno == errno.EEXIST:
1530
 
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1570
            osutils.ensure_empty_directory_exists(
 
1571
                deletiondir,
 
1572
                errors.ExistingPendingDeletion)
1531
1573
        except:
1532
1574
            tree.unlock()
1533
1575
            raise
1596
1638
            else:
1597
1639
                raise
1598
1640
        if typefunc(mode):
1599
 
            os.chmod(self._limbo_name(trans_id), mode)
 
1641
            osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1600
1642
 
1601
1643
    def iter_tree_children(self, parent_id):
1602
1644
        """Iterate through the entry's tree children, if any"""
1685
1727
        child_pb = ui.ui_factory.nested_progress_bar()
1686
1728
        try:
1687
1729
            if precomputed_delta is None:
1688
 
                child_pb.update('Apply phase', 0, 2)
 
1730
                child_pb.update(gettext('Apply phase'), 0, 2)
1689
1731
                inventory_delta = self._generate_inventory_delta()
1690
1732
                offset = 1
1691
1733
            else:
1696
1738
            else:
1697
1739
                mover = _mover
1698
1740
            try:
1699
 
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1741
                child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1700
1742
                self._apply_removals(mover)
1701
 
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1743
                child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1702
1744
                modified_paths = self._apply_insertions(mover)
1703
1745
            except:
1704
1746
                mover.rollback()
1707
1749
                mover.apply_deletions()
1708
1750
        finally:
1709
1751
            child_pb.finished()
 
1752
        if self.final_file_id(self.root) is None:
 
1753
            inventory_delta = [e for e in inventory_delta if e[0] != '']
1710
1754
        self._tree.apply_inventory_delta(inventory_delta)
1711
1755
        self._apply_observed_sha1s()
1712
1756
        self._done = True
1722
1766
        try:
1723
1767
            for num, trans_id in enumerate(self._removed_id):
1724
1768
                if (num % 10) == 0:
1725
 
                    child_pb.update('removing file', num, total_entries)
 
1769
                    child_pb.update(gettext('removing file'), num, total_entries)
1726
1770
                if trans_id == self._new_root:
1727
1771
                    file_id = self._tree.get_root_id()
1728
1772
                else:
1740
1784
            final_kinds = {}
1741
1785
            for num, (path, trans_id) in enumerate(new_paths):
1742
1786
                if (num % 10) == 0:
1743
 
                    child_pb.update('adding file',
 
1787
                    child_pb.update(gettext('adding file'),
1744
1788
                                    num + len(self._removed_id), total_entries)
1745
1789
                file_id = new_path_file_ids[trans_id]
1746
1790
                if file_id is None:
1786
1830
        tree_paths.sort(reverse=True)
1787
1831
        child_pb = ui.ui_factory.nested_progress_bar()
1788
1832
        try:
1789
 
            for num, data in enumerate(tree_paths):
1790
 
                path, trans_id = data
1791
 
                child_pb.update('removing file', num, len(tree_paths))
 
1833
            for num, (path, trans_id) in enumerate(tree_paths):
 
1834
                # do not attempt to move root into a subdirectory of itself.
 
1835
                if path == '':
 
1836
                    continue
 
1837
                child_pb.update(gettext('removing file'), num, len(tree_paths))
1792
1838
                full_path = self._tree.abspath(path)
1793
1839
                if trans_id in self._removed_contents:
1794
1840
                    delete_path = os.path.join(self._deletiondir, trans_id)
1823
1869
        try:
1824
1870
            for num, (path, trans_id) in enumerate(new_paths):
1825
1871
                if (num % 10) == 0:
1826
 
                    child_pb.update('adding file', num, len(new_paths))
 
1872
                    child_pb.update(gettext('adding file'), num, len(new_paths))
1827
1873
                full_path = self._tree.abspath(path)
1828
1874
                if trans_id in self._needs_rename:
1829
1875
                    try:
2213
2259
        else:
2214
2260
            return None
2215
2261
 
 
2262
    def get_file_verifier(self, file_id, path=None, stat_value=None):
 
2263
        trans_id = self._transform.trans_id_file_id(file_id)
 
2264
        kind = self._transform._new_contents.get(trans_id)
 
2265
        if kind is None:
 
2266
            return self._transform._tree.get_file_verifier(file_id)
 
2267
        if kind == 'file':
 
2268
            fileobj = self.get_file(file_id)
 
2269
            try:
 
2270
                return ("SHA1", sha_file(fileobj))
 
2271
            finally:
 
2272
                fileobj.close()
 
2273
 
2216
2274
    def get_file_sha1(self, file_id, path=None, stat_value=None):
2217
2275
        trans_id = self._transform.trans_id_file_id(file_id)
2218
2276
        kind = self._transform._new_contents.get(trans_id)
2268
2326
            if kind == 'file':
2269
2327
                statval = os.lstat(limbo_name)
2270
2328
                size = statval.st_size
2271
 
                if not supports_executable():
 
2329
                if not tt._limbo_supports_executable():
2272
2330
                    executable = False
2273
2331
                else:
2274
2332
                    executable = statval.st_mode & S_IEXEC
2489
2547
    file_trans_id = {}
2490
2548
    top_pb = ui.ui_factory.nested_progress_bar()
2491
2549
    pp = ProgressPhase("Build phase", 2, top_pb)
2492
 
    if tree.inventory.root is not None:
 
2550
    if tree.get_root_id() is not None:
2493
2551
        # This is kind of a hack: we should be altering the root
2494
2552
        # as part of the regular tree shape diff logic.
2495
2553
        # The conditional test here is to avoid doing an
2510
2568
        try:
2511
2569
            deferred_contents = []
2512
2570
            num = 0
2513
 
            total = len(tree.inventory)
 
2571
            total = len(tree.all_file_ids())
2514
2572
            if delta_from_tree:
2515
2573
                precomputed_delta = []
2516
2574
            else:
2525
2583
                for dir, files in wt.walkdirs():
2526
2584
                    existing_files.update(f[0] for f in files)
2527
2585
            for num, (tree_path, entry) in \
2528
 
                enumerate(tree.inventory.iter_entries_by_dir()):
2529
 
                pb.update("Building tree", num - len(deferred_contents), total)
 
2586
                enumerate(tree.iter_entries_by_dir()):
 
2587
                pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2530
2588
                if entry.parent_id is None:
2531
2589
                    continue
2532
2590
                reparent = False
2538
2596
                    kind = file_kind(target_path)
2539
2597
                    if kind == "directory":
2540
2598
                        try:
2541
 
                            bzrdir.BzrDir.open(target_path)
 
2599
                            controldir.ControlDir.open(target_path)
2542
2600
                        except errors.NotBranchError:
2543
2601
                            pass
2544
2602
                        else:
2616
2674
                new_desired_files.append((file_id,
2617
2675
                    (trans_id, tree_path, text_sha1)))
2618
2676
                continue
2619
 
            pb.update('Adding file contents', count + offset, total)
 
2677
            pb.update(gettext('Adding file contents'), count + offset, total)
2620
2678
            if hardlink:
2621
2679
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2622
2680
                                   trans_id)
2643
2701
            contents = filtered_output_bytes(contents, filters,
2644
2702
                ContentFilterContext(tree_path, tree))
2645
2703
        tt.create_file(contents, trans_id, sha1=text_sha1)
2646
 
        pb.update('Adding file contents', count + offset, total)
 
2704
        pb.update(gettext('Adding file contents'), count + offset, total)
2647
2705
 
2648
2706
 
2649
2707
def _reparent_children(tt, old_parent, new_parent):
2781
2839
            return new_name
2782
2840
 
2783
2841
 
2784
 
def _entry_changes(file_id, entry, working_tree):
2785
 
    """Determine in which ways the inventory entry has changed.
2786
 
 
2787
 
    Returns booleans: has_contents, content_mod, meta_mod
2788
 
    has_contents means there are currently contents, but they differ
2789
 
    contents_mod means contents need to be modified
2790
 
    meta_mod means the metadata needs to be modified
2791
 
    """
2792
 
    cur_entry = working_tree.inventory[file_id]
2793
 
    try:
2794
 
        working_kind = working_tree.kind(file_id)
2795
 
        has_contents = True
2796
 
    except NoSuchFile:
2797
 
        has_contents = False
2798
 
        contents_mod = True
2799
 
        meta_mod = False
2800
 
    if has_contents is True:
2801
 
        if entry.kind != working_kind:
2802
 
            contents_mod, meta_mod = True, False
2803
 
        else:
2804
 
            cur_entry._read_tree_state(working_tree.id2path(file_id),
2805
 
                                       working_tree)
2806
 
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
2807
 
            cur_entry._forget_tree_state()
2808
 
    return has_contents, contents_mod, meta_mod
2809
 
 
2810
 
 
2811
2842
def revert(working_tree, target_tree, filenames, backups=False,
2812
2843
           pb=None, change_reporter=None):
2813
2844
    """Revert a working tree's contents to those of a target tree."""
2895
2926
                        if basis_tree is None:
2896
2927
                            basis_tree = working_tree.basis_tree()
2897
2928
                            basis_tree.lock_read()
2898
 
                        if file_id in basis_tree:
 
2929
                        if basis_tree.has_id(file_id):
2899
2930
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
2900
2931
                                keep_content = True
2901
2932
                        elif target_kind is None and not target_versioned:
2931
2962
                        basis_tree = working_tree.basis_tree()
2932
2963
                        basis_tree.lock_read()
2933
2964
                    new_sha1 = target_tree.get_file_sha1(file_id)
2934
 
                    if (file_id in basis_tree and new_sha1 ==
2935
 
                        basis_tree.get_file_sha1(file_id)):
 
2965
                    if (basis_tree.has_id(file_id) and
 
2966
                        new_sha1 == basis_tree.get_file_sha1(file_id)):
2936
2967
                        if file_id in merge_modified:
2937
2968
                            del merge_modified[file_id]
2938
2969
                    else:
2990
3021
    pb = ui.ui_factory.nested_progress_bar()
2991
3022
    try:
2992
3023
        for n in range(10):
2993
 
            pb.update('Resolution pass', n+1, 10)
 
3024
            pb.update(gettext('Resolution pass'), n+1, 10)
2994
3025
            conflicts = tt.find_conflicts()
2995
3026
            if len(conflicts) == 0:
2996
3027
                return new_conflicts
3020
3051
                existing_file, new_file = conflict[2], conflict[1]
3021
3052
            else:
3022
3053
                existing_file, new_file = conflict[1], conflict[2]
3023
 
            new_name = tt.final_name(existing_file)+'.moved'
 
3054
            new_name = tt.final_name(existing_file) + '.moved'
3024
3055
            tt.adjust_path(new_name, final_parent, existing_file)
3025
3056
            new_conflicts.add((c_type, 'Moved existing file to',
3026
3057
                               existing_file, new_file))
3089
3120
        elif c_type == 'unversioned parent':
3090
3121
            file_id = tt.inactive_file_id(conflict[1])
3091
3122
            # special-case the other tree root (move its children instead)
3092
 
            if path_tree and file_id in path_tree:
 
3123
            if path_tree and path_tree.has_id(file_id):
3093
3124
                if path_tree.path2id('') == file_id:
3094
3125
                    # This is the root entry, skip it
3095
3126
                    continue
3113
3144
 
3114
3145
def cook_conflicts(raw_conflicts, tt):
3115
3146
    """Generate a list of cooked conflicts, sorted by file path"""
3116
 
    from bzrlib.conflicts import Conflict
3117
3147
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3118
 
    return sorted(conflict_iter, key=Conflict.sort_key)
 
3148
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
3119
3149
 
3120
3150
 
3121
3151
def iter_cook_conflicts(raw_conflicts, tt):
3122
 
    from bzrlib.conflicts import Conflict
3123
3152
    fp = FinalPaths(tt)
3124
3153
    for conflict in raw_conflicts:
3125
3154
        c_type = conflict[0]
3127
3156
        modified_path = fp.get_path(conflict[2])
3128
3157
        modified_id = tt.final_file_id(conflict[2])
3129
3158
        if len(conflict) == 3:
3130
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
3131
 
                                     file_id=modified_id)
 
3159
            yield conflicts.Conflict.factory(
 
3160
                c_type, action=action, path=modified_path, file_id=modified_id)
3132
3161
 
3133
3162
        else:
3134
3163
            conflicting_path = fp.get_path(conflict[3])
3135
3164
            conflicting_id = tt.final_file_id(conflict[3])
3136
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
3137
 
                                   file_id=modified_id,
3138
 
                                   conflict_path=conflicting_path,
3139
 
                                   conflict_file_id=conflicting_id)
 
3165
            yield conflicts.Conflict.factory(
 
3166
                c_type, action=action, path=modified_path,
 
3167
                file_id=modified_id,
 
3168
                conflict_path=conflicting_path,
 
3169
                conflict_file_id=conflicting_id)
3140
3170
 
3141
3171
 
3142
3172
class _FileMover(object):