~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

(jameinel) (bug #780544) when updating the WT,
 allow it to be done with a fast delta,
 rather than setting the state from scratch. (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
    bencode,
33
33
    bzrdir,
34
34
    commit,
35
 
    conflicts,
36
35
    delta,
37
36
    errors,
38
37
    inventory,
138
137
        # A counter of how many files have been renamed
139
138
        self.rename_count = 0
140
139
 
141
 
    def __enter__(self):
142
 
        """Support Context Manager API."""
143
 
        return self
144
 
 
145
 
    def __exit__(self, exc_type, exc_val, exc_tb):
146
 
        """Support Context Manager API."""
147
 
        self.finalize()
148
 
 
149
140
    def finalize(self):
150
141
        """Release the working tree lock, if held.
151
142
 
226
217
        This means that the old root trans-id becomes obsolete, so it is
227
218
        recommended only to invoke this after the root trans-id has become
228
219
        irrelevant.
229
 
 
230
220
        """
231
221
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
232
222
                     ROOT_PARENT]
233
223
        if len(new_roots) < 1:
234
 
            if self.final_kind(self.root) is None:
235
 
                self.cancel_deletion(self.root)
236
 
            if self.final_file_id(self.root) is None:
237
 
                self.version_file(self.tree_file_id(self.root),
238
 
                                     self.root)
239
224
            return
240
225
        if len(new_roots) != 1:
241
226
            raise ValueError('A tree cannot have two roots!')
243
228
            self._new_root = new_roots[0]
244
229
            return
245
230
        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
 
246
236
        # unversion the new root's directory.
247
 
        if self.final_kind(self._new_root) is None:
248
 
            file_id = self.final_file_id(old_new_root)
249
 
        else:
250
 
            file_id = self.final_file_id(self._new_root)
 
237
        file_id = self.final_file_id(old_new_root)
251
238
        if old_new_root in self._new_id:
252
239
            self.cancel_versioning(old_new_root)
253
240
        else:
257
244
        if (self.tree_file_id(self._new_root) is not None and
258
245
            self._new_root not in self._removed_id):
259
246
            self.unversion_file(self._new_root)
260
 
        if file_id is not None:
261
 
            self.version_file(file_id, self._new_root)
 
247
        self.version_file(file_id, self._new_root)
262
248
 
263
249
        # Now move children of new root into old root directory.
264
250
        # Ensure all children are registered with the transaction, but don't
398
384
        return sorted(FinalPaths(self).get_paths(new_ids))
399
385
 
400
386
    def _inventory_altered(self):
401
 
        """Determine which trans_ids need new Inventory entries.
402
 
 
403
 
        An new entry is needed when anything that would be reflected by an
404
 
        inventory entry changes, including file name, file_id, parent file_id,
405
 
        file kind, and the execute bit.
406
 
 
407
 
        Some care is taken to return entries with real changes, not cases
408
 
        where the value is deleted and then restored to its original value,
409
 
        but some actually unchanged values may be returned.
410
 
 
411
 
        :returns: A list of (path, trans_id) for all items requiring an
412
 
            inventory change. Ordered by path.
413
 
        """
414
 
        changed_ids = set()
415
 
        # Find entries whose file_ids are new (or changed).
416
 
        new_file_id = set(t for t in self._new_id
417
 
                          if self._new_id[t] != self.tree_file_id(t))
418
 
        for id_set in [self._new_name, self._new_parent, new_file_id,
 
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,
419
390
                       self._new_executability]:
420
 
            changed_ids.update(id_set)
421
 
        # removing implies a kind change
 
391
            new_ids.update(id_set)
422
392
        changed_kind = set(self._removed_contents)
423
 
        # so does adding
424
393
        changed_kind.intersection_update(self._new_contents)
425
 
        # Ignore entries that are already known to have changed.
426
 
        changed_kind.difference_update(changed_ids)
427
 
        #  to keep only the truly changed ones
 
394
        changed_kind.difference_update(new_ids)
428
395
        changed_kind = (t for t in changed_kind
429
396
                        if self.tree_kind(t) != self.final_kind(t))
430
 
        # all kind changes will alter the inventory
431
 
        changed_ids.update(changed_kind)
432
 
        # To find entries with changed parent_ids, find parents which existed,
433
 
        # but changed file_id.
434
 
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
435
 
        # Now add all their children to the set.
436
 
        for parent_trans_id in new_file_id:
437
 
            changed_ids.update(self.iter_tree_children(parent_trans_id))
438
 
        return sorted(FinalPaths(self).get_paths(changed_ids))
 
397
        new_ids.update(changed_kind)
 
398
        return sorted(FinalPaths(self).get_paths(new_ids))
439
399
 
440
400
    def final_kind(self, trans_id):
441
401
        """Determine the final file kind, after any changes applied.
1194
1154
        self._deletiondir = None
1195
1155
        # A mapping of transform ids to their limbo filename
1196
1156
        self._limbo_files = {}
1197
 
        self._possibly_stale_limbo_files = set()
1198
1157
        # A mapping of transform ids to a set of the transform ids of children
1199
1158
        # that their limbo directory has
1200
1159
        self._limbo_children = {}
1213
1172
        if self._tree is None:
1214
1173
            return
1215
1174
        try:
1216
 
            limbo_paths = self._limbo_files.values() + list(
1217
 
                self._possibly_stale_limbo_files)
1218
 
            limbo_paths = sorted(limbo_paths, reverse=True)
1219
 
            for path in limbo_paths:
1220
 
                try:
1221
 
                    delete_any(path)
1222
 
                except OSError, e:
1223
 
                    if e.errno != errno.ENOENT:
1224
 
                        raise
1225
 
                    # XXX: warn? perhaps we just got interrupted at an
1226
 
                    # inconvenient moment, but perhaps files are disappearing
1227
 
                    # from under us?
 
1175
            entries = [(self._limbo_name(t), t, k) for t, k in
 
1176
                       self._new_contents.iteritems()]
 
1177
            entries.sort(reverse=True)
 
1178
            for path, trans_id, kind in entries:
 
1179
                delete_any(path)
1228
1180
            try:
1229
1181
                delete_any(self._limbodir)
1230
1182
            except OSError:
1279
1231
        entries from _limbo_files, because they are now stale.
1280
1232
        """
1281
1233
        for trans_id in trans_ids:
1282
 
            old_path = self._limbo_files[trans_id]
1283
 
            self._possibly_stale_limbo_files.add(old_path)
1284
 
            del self._limbo_files[trans_id]
 
1234
            old_path = self._limbo_files.pop(trans_id)
1285
1235
            if trans_id not in self._new_contents:
1286
1236
                continue
1287
1237
            new_path = self._limbo_name(trans_id)
1288
1238
            os.rename(old_path, new_path)
1289
 
            self._possibly_stale_limbo_files.remove(old_path)
1290
1239
            for descendant in self._limbo_descendants(trans_id):
1291
1240
                desc_path = self._limbo_files[descendant]
1292
1241
                desc_path = new_path + desc_path[len(old_path):]
1316
1265
        name = self._limbo_name(trans_id)
1317
1266
        f = open(name, 'wb')
1318
1267
        try:
1319
 
            unique_add(self._new_contents, trans_id, 'file')
 
1268
            try:
 
1269
                unique_add(self._new_contents, trans_id, 'file')
 
1270
            except:
 
1271
                # Clean up the file, it never got registered so
 
1272
                # TreeTransform.finalize() won't clean it up.
 
1273
                f.close()
 
1274
                os.unlink(name)
 
1275
                raise
1320
1276
            f.writelines(contents)
1321
1277
        finally:
1322
1278
            f.close()
1832
1788
        tree_paths.sort(reverse=True)
1833
1789
        child_pb = ui.ui_factory.nested_progress_bar()
1834
1790
        try:
1835
 
            for num, (path, trans_id) in enumerate(tree_paths):
1836
 
                # do not attempt to move root into a subdirectory of itself.
1837
 
                if path == '':
1838
 
                    continue
 
1791
            for num, data in enumerate(tree_paths):
 
1792
                path, trans_id = data
1839
1793
                child_pb.update('removing file', num, len(tree_paths))
1840
1794
                full_path = self._tree.abspath(path)
1841
1795
                if trans_id in self._removed_contents:
1897
1851
                    self._observed_sha1s[trans_id] = (o_sha1, st)
1898
1852
        finally:
1899
1853
            child_pb.finished()
1900
 
        for path, trans_id in new_paths:
1901
 
            # new_paths includes stuff like workingtree conflicts. Only the
1902
 
            # stuff in new_contents actually comes from limbo.
1903
 
            if trans_id in self._limbo_files:
1904
 
                del self._limbo_files[trans_id]
1905
1854
        self._new_contents.clear()
1906
1855
        return modified_paths
1907
1856
 
2629
2578
            precomputed_delta = None
2630
2579
        conflicts = cook_conflicts(raw_conflicts, tt)
2631
2580
        for conflict in conflicts:
2632
 
            trace.warning(unicode(conflict))
 
2581
            trace.warning(conflict)
2633
2582
        try:
2634
2583
            wt.add_conflicts(conflicts)
2635
2584
        except errors.UnsupportedOperation:
2871
2820
                unversioned_filter=working_tree.is_ignored)
2872
2821
            delta.report_changes(tt.iter_changes(), change_reporter)
2873
2822
        for conflict in conflicts:
2874
 
            trace.warning(unicode(conflict))
 
2823
            trace.warning(conflict)
2875
2824
        pp.next_phase()
2876
2825
        tt.apply()
2877
2826
        working_tree.set_merge_modified(merge_modified)
2943
2892
                        if basis_tree is None:
2944
2893
                            basis_tree = working_tree.basis_tree()
2945
2894
                            basis_tree.lock_read()
2946
 
                        if basis_tree.has_id(file_id):
 
2895
                        if file_id in basis_tree:
2947
2896
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
2948
2897
                                keep_content = True
2949
2898
                        elif target_kind is None and not target_versioned:
2979
2928
                        basis_tree = working_tree.basis_tree()
2980
2929
                        basis_tree.lock_read()
2981
2930
                    new_sha1 = target_tree.get_file_sha1(file_id)
2982
 
                    if (basis_tree.has_id(file_id) and
2983
 
                        new_sha1 == basis_tree.get_file_sha1(file_id)):
 
2931
                    if (file_id in basis_tree and new_sha1 ==
 
2932
                        basis_tree.get_file_sha1(file_id)):
2984
2933
                        if file_id in merge_modified:
2985
2934
                            del merge_modified[file_id]
2986
2935
                    else:
3137
3086
        elif c_type == 'unversioned parent':
3138
3087
            file_id = tt.inactive_file_id(conflict[1])
3139
3088
            # special-case the other tree root (move its children instead)
3140
 
            if path_tree and path_tree.has_id(file_id):
 
3089
            if path_tree and file_id in path_tree:
3141
3090
                if path_tree.path2id('') == file_id:
3142
3091
                    # This is the root entry, skip it
3143
3092
                    continue
3161
3110
 
3162
3111
def cook_conflicts(raw_conflicts, tt):
3163
3112
    """Generate a list of cooked conflicts, sorted by file path"""
 
3113
    from bzrlib.conflicts import Conflict
3164
3114
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3165
 
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
 
3115
    return sorted(conflict_iter, key=Conflict.sort_key)
3166
3116
 
3167
3117
 
3168
3118
def iter_cook_conflicts(raw_conflicts, tt):
 
3119
    from bzrlib.conflicts import Conflict
3169
3120
    fp = FinalPaths(tt)
3170
3121
    for conflict in raw_conflicts:
3171
3122
        c_type = conflict[0]
3173
3124
        modified_path = fp.get_path(conflict[2])
3174
3125
        modified_id = tt.final_file_id(conflict[2])
3175
3126
        if len(conflict) == 3:
3176
 
            yield conflicts.Conflict.factory(
3177
 
                c_type, action=action, path=modified_path, file_id=modified_id)
 
3127
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
3128
                                     file_id=modified_id)
3178
3129
 
3179
3130
        else:
3180
3131
            conflicting_path = fp.get_path(conflict[3])
3181
3132
            conflicting_id = tt.final_file_id(conflict[3])
3182
 
            yield conflicts.Conflict.factory(
3183
 
                c_type, action=action, path=modified_path,
3184
 
                file_id=modified_id,
3185
 
                conflict_path=conflicting_path,
3186
 
                conflict_file_id=conflicting_id)
 
3133
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
3134
                                   file_id=modified_id,
 
3135
                                   conflict_path=conflicting_path,
 
3136
                                   conflict_file_id=conflicting_id)
3187
3137
 
3188
3138
 
3189
3139
class _FileMover(object):