/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
1
# Copyright (C) 2008 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""CommitHandlers that build and save revisions & their inventories."""
18
19
20
from bzrlib import (
21
    errors,
22
    generate_ids,
23
    inventory,
24
    osutils,
25
    revision,
0.64.192 by Ian Clatworthy
delegate commit message escaping to the serializer if it's a modern one
26
    serializer,
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
27
    )
28
from bzrlib.plugins.fastimport import helpers, processor
29
30
0.64.192 by Ian Clatworthy
delegate commit message escaping to the serializer if it's a modern one
31
_serializer_handles_escaping = hasattr(serializer.Serializer,
32
    'squashes_xml_invalid_characters')
33
34
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
35
def copy_inventory(inv):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
36
    # This currently breaks revision-id matching
37
    #if hasattr(inv, "_get_mutable_inventory"):
38
    #    # TODO: Make this a public API on inventory
39
    #    return inv._get_mutable_inventory()
40
41
    # TODO: Shallow copy - deep inventory copying is expensive
42
    return inv.copy()
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
43
44
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
45
class GenericCommitHandler(processor.CommitHandler):
46
    """Base class for Bazaar CommitHandlers."""
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
47
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
48
    def __init__(self, command, cache_mgr, rev_store, verbose=False,
49
        prune_empty_dirs=True):
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
50
        super(GenericCommitHandler, self).__init__(command)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
51
        self.cache_mgr = cache_mgr
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
52
        self.rev_store = rev_store
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
53
        self.verbose = verbose
0.64.159 by Ian Clatworthy
make the file-id cache optional and branch-ref aware
54
        self.branch_ref = command.ref
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
55
        self.prune_empty_dirs = prune_empty_dirs
0.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
56
        # This tracks path->file-id for things we're creating this commit.
57
        # If the same path is created multiple times, we need to warn the
58
        # user and add it just once.
59
        self._new_file_ids = {}
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
60
        # This tracks the paths for things we're deleting this commit.
61
        # If the same path is added or the destination of a rename say,
62
        # then a fresh file-id is required.
63
        self._paths_deleted_this_commit = set()
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
64
65
    def pre_process_files(self):
66
        """Prepare for committing."""
67
        self.revision_id = self.gen_revision_id()
68
        # cache of texts for this commit, indexed by file-id
69
        self.lines_for_commit = {}
0.64.171 by Ian Clatworthy
use inv deltas by default for all formats now: --classic to get old algorithm for packs
70
        #if self.rev_store.expects_rich_root():
71
        self.lines_for_commit[inventory.ROOT_ID] = []
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
72
73
        # Track the heads and get the real parent list
74
        parents = self.cache_mgr.track_heads(self.command)
75
76
        # Convert the parent commit-ids to bzr revision-ids
77
        if parents:
78
            self.parents = [self.cache_mgr.revision_ids[p]
79
                for p in parents]
80
        else:
81
            self.parents = []
82
        self.debug("%s id: %s, parents: %s", self.command.id,
83
            self.revision_id, str(self.parents))
84
0.85.2 by Ian Clatworthy
improve per-file graph generation
85
        # Tell the RevisionStore we're starting a new commit
86
        self.revision = self.build_revision()
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
87
        self.parent_invs = [self.get_inventory(p) for p in self.parents]
0.85.2 by Ian Clatworthy
improve per-file graph generation
88
        self.rev_store.start_new_revision(self.revision, self.parents,
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
89
            self.parent_invs)
0.85.2 by Ian Clatworthy
improve per-file graph generation
90
91
        # cache of per-file parents for this commit, indexed by file-id
92
        self.per_file_parents_for_commit = {}
93
        if self.rev_store.expects_rich_root():
0.64.160 by Ian Clatworthy
make per-file parents tuples and fix text loading in chk formats
94
            self.per_file_parents_for_commit[inventory.ROOT_ID] = ()
0.85.2 by Ian Clatworthy
improve per-file graph generation
95
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
96
        # Keep the basis inventory. This needs to be treated as read-only.
97
        if len(self.parents) == 0:
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
98
            self.basis_inventory = self._init_inventory()
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
99
        else:
100
            self.basis_inventory = self.get_inventory(self.parents[0])
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
101
        if hasattr(self.basis_inventory, "root_id"):
102
            self.inventory_root_id = self.basis_inventory.root_id
103
        else:
104
            self.inventory_root_id = self.basis_inventory.root.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
105
106
        # directory-path -> inventory-entry for current inventory
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
107
        self.directory_entries = {}
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
108
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
109
    def _init_inventory(self):
110
        return self.rev_store.init_inventory(self.revision_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
111
112
    def get_inventory(self, revision_id):
113
        """Get the inventory for a revision id."""
114
        try:
115
            inv = self.cache_mgr.inventories[revision_id]
116
        except KeyError:
117
            if self.verbose:
0.64.148 by Ian Clatworthy
handle delete of unknown file in chk formats & reduce noise
118
                self.mutter("get_inventory cache miss for %s", revision_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
119
            # Not cached so reconstruct from the RevisionStore
120
            inv = self.rev_store.get_inventory(revision_id)
121
            self.cache_mgr.inventories[revision_id] = inv
122
        return inv
123
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
124
    def _get_lines(self, file_id):
125
        """Get the lines for a file-id."""
126
        return self.lines_for_commit[file_id]
127
0.85.2 by Ian Clatworthy
improve per-file graph generation
128
    def _get_per_file_parents(self, file_id):
129
        """Get the lines for a file-id."""
130
        return self.per_file_parents_for_commit[file_id]
131
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
132
    def _get_inventories(self, revision_ids):
133
        """Get the inventories for revision-ids.
134
        
135
        This is a callback used by the RepositoryStore to
136
        speed up inventory reconstruction.
137
        """
138
        present = []
139
        inventories = []
140
        # If an inventory is in the cache, we assume it was
141
        # successfully loaded into the revision store
142
        for revision_id in revision_ids:
143
            try:
144
                inv = self.cache_mgr.inventories[revision_id]
145
                present.append(revision_id)
146
            except KeyError:
147
                if self.verbose:
148
                    self.note("get_inventories cache miss for %s", revision_id)
149
                # Not cached so reconstruct from the revision store
150
                try:
151
                    inv = self.get_inventory(revision_id)
152
                    present.append(revision_id)
153
                except:
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
154
                    inv = self._init_inventory()
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
155
                self.cache_mgr.inventories[revision_id] = inv
156
            inventories.append(inv)
157
        return present, inventories
158
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
159
    def bzr_file_id_and_new(self, path):
160
        """Get a Bazaar file identifier and new flag for a path.
161
        
162
        :return: file_id, is_new where
163
          is_new = True if the file_id is newly created
164
        """
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
165
        if path not in self._paths_deleted_this_commit:
166
            # Try the basis inventory
167
            id = self.basis_inventory.path2id(path)
168
            if id is not None:
169
                return id, False
170
            
171
            # Try the other inventories
172
            if len(self.parents) > 1:
173
                for inv in self.parent_invs[1:]:
174
                    id = self.basis_inventory.path2id(path)
175
                    if id is not None:
176
                        return id, False
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
177
178
        # Doesn't exist yet so create it
179
        id = generate_ids.gen_file_id(path)
180
        self.debug("Generated new file id %s for '%s' in revision-id '%s'",
181
            id, path, self.revision_id)
0.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
182
        self._new_file_ids[path] = id
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
183
        return id, True
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
184
185
    def bzr_file_id(self, path):
186
        """Get a Bazaar file identifier for a path."""
187
        return self.bzr_file_id_and_new(path)[0]
188
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
189
    def _format_name_email(self, name, email):
190
        """Format name & email as a string."""
191
        if email:
192
            return "%s <%s>" % (name, email)
193
        else:
194
            return name
195
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
196
    def gen_revision_id(self):
197
        """Generate a revision id.
198
199
        Subclasses may override this to produce deterministic ids say.
200
        """
201
        committer = self.command.committer
202
        # Perhaps 'who' being the person running the import is ok? If so,
203
        # it might be a bit quicker and give slightly better compression?
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
204
        who = self._format_name_email(committer[0], committer[1])
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
205
        timestamp = committer[2]
206
        return generate_ids.gen_revision_id(who, timestamp)
207
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
208
    def build_revision(self):
209
        rev_props = {}
210
        committer = self.command.committer
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
211
        who = self._format_name_email(committer[0], committer[1])
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
212
        author = self.command.author
213
        if author is not None:
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
214
            author_id = self._format_name_email(author[0], author[1])
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
215
            if author_id != who:
216
                rev_props['author'] = author_id
0.64.192 by Ian Clatworthy
delegate commit message escaping to the serializer if it's a modern one
217
        message = self.command.message
218
        if not _serializer_handles_escaping:
219
            # We need to assume the bad ol' days
220
            message = helpers.escape_commit_message(message)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
221
        return revision.Revision(
222
           timestamp=committer[2],
223
           timezone=committer[3],
224
           committer=who,
0.64.192 by Ian Clatworthy
delegate commit message escaping to the serializer if it's a modern one
225
           message=message,
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
226
           revision_id=self.revision_id,
227
           properties=rev_props,
228
           parent_ids=self.parents)
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
229
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
230
    def _modify_item(self, path, kind, is_executable, data, inv):
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
231
        """Add to or change an item in the inventory."""
0.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
232
        # If we've already added this, warn the user that we're ignoring it.
233
        # In the future, it might be nice to double check that the new data
234
        # is the same as the old but, frankly, exporters should be fixed
235
        # not to produce bad data streams in the first place ...
236
        existing = self._new_file_ids.get(path)
237
        if existing:
238
            self.warning("%s already added in this commit - ignoring" % (path,))
239
            return
240
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
241
        # Create the new InventoryEntry
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
242
        basename, parent_id = self._ensure_directory(path, inv)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
243
        file_id = self.bzr_file_id(path)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
244
        ie = inventory.make_entry(kind, basename, parent_id, file_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
245
        ie.revision = self.revision_id
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
246
        if kind == 'file':
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
247
            ie.executable = is_executable
248
            lines = osutils.split_lines(data)
249
            ie.text_sha1 = osutils.sha_strings(lines)
250
            ie.text_size = sum(map(len, lines))
251
            self.lines_for_commit[file_id] = lines
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
252
        elif kind == 'symlink':
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
253
            ie.symlink_target = data.encode('utf8')
254
            # There are no lines stored for a symlink so
255
            # make sure the cache used by get_lines knows that
256
            self.lines_for_commit[file_id] = []
257
        else:
258
            raise errors.BzrError("Cannot import items of kind '%s' yet" %
259
                (kind,))
260
        # Record it
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
261
        if file_id in inv:
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
262
            old_ie = inv[file_id]
263
            if old_ie.kind == 'directory':
264
                self.record_delete(path, old_ie)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
265
            self.record_changed(path, ie, parent_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
266
        else:
0.64.165 by Ian Clatworthy
handle adding a file to a dir deleted in the same commit
267
            try:
268
                self.record_new(path, ie)
269
            except:
0.64.167 by Ian Clatworthy
incremental packing for chk formats
270
                print "failed to add path '%s' with entry '%s' in command %s" \
271
                    % (path, ie, self.command.id)
272
                print "parent's children are:\n%r\n" % (ie.parent_id.children,)
0.64.165 by Ian Clatworthy
handle adding a file to a dir deleted in the same commit
273
                raise
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
274
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
275
    def _ensure_directory(self, path, inv):
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
276
        """Ensure that the containing directory exists for 'path'"""
277
        dirname, basename = osutils.split(path)
278
        if dirname == '':
279
            # the root node doesn't get updated
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
280
            return basename, self.inventory_root_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
281
        try:
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
282
            ie = self._get_directory_entry(inv, dirname)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
283
        except KeyError:
284
            # We will create this entry, since it doesn't exist
285
            pass
286
        else:
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
287
            return basename, ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
288
289
        # No directory existed, we will just create one, first, make sure
290
        # the parent exists
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
291
        dir_basename, parent_id = self._ensure_directory(dirname, inv)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
292
        dir_file_id = self.bzr_file_id(dirname)
293
        ie = inventory.entry_factory['directory'](dir_file_id,
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
294
            dir_basename, parent_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
295
        ie.revision = self.revision_id
296
        self.directory_entries[dirname] = ie
297
        # There are no lines stored for a directory so
298
        # make sure the cache used by get_lines knows that
299
        self.lines_for_commit[dir_file_id] = []
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
300
301
        # It's possible that a file or symlink with that file-id
302
        # already exists. If it does, we need to delete it.
303
        if dir_file_id in inv:
304
            self.record_delete(dirname, ie)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
305
        self.record_new(dirname, ie)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
306
        return basename, ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
307
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
308
    def _get_directory_entry(self, inv, dirname):
309
        """Get the inventory entry for a directory.
310
        
311
        Raises KeyError if dirname is not a directory in inv.
312
        """
313
        result = self.directory_entries.get(dirname)
314
        if result is None:
0.64.146 by Ian Clatworthy
fix first file is in a subdirectory bug for chk formats
315
            try:
316
                file_id = inv.path2id(dirname)
317
            except errors.NoSuchId:
318
                # In a CHKInventory, this is raised if there's no root yet
319
                raise KeyError
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
320
            if file_id is None:
321
                raise KeyError
322
            result = inv[file_id]
323
            # dirname must be a directory for us to return it
324
            if result.kind == 'directory':
325
                self.directory_entries[dirname] = result
326
            else:
327
                raise KeyError
328
        return result
329
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
330
    def _delete_item(self, path, inv):
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
331
        newly_added = self._new_file_ids.get(path)
332
        if newly_added:
333
            # We've only just added this path earlier in this commit.
334
            file_id = newly_added
335
            # note: delta entries look like (old, new, file-id, ie)
336
            ie = self._delta_entries_by_fileid[file_id][3]
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
337
        else:
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
338
            file_id = inv.path2id(path)
339
            if file_id is None:
340
                self.mutter("ignoring delete of %s as not in inventory", path)
341
                return
342
            try:
343
                ie = inv[file_id]
344
            except errors.NoSuchId:
345
                self.mutter("ignoring delete of %s as not in inventory", path)
346
                return
347
        self.record_delete(path, ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
348
349
    def _copy_item(self, src_path, dest_path, inv):
0.99.8 by Ian Clatworthy
handle copy of a newly added file
350
        newly_added = self._new_file_ids.get(src_path)
351
        if newly_added:
352
            # We've only just added this path earlier in this commit.
353
            file_id = newly_added
354
            # note: delta entries look like (old, new, file-id, ie)
355
            ie = self._delta_entries_by_fileid[file_id][3]
356
        else:
357
            file_id = inv.path2id(src_path)
358
            if file_id is None:
359
                self.warning("ignoring copy of %s to %s - source does not exist",
360
                    src_path, dest_path)
361
                return
362
            ie = inv[file_id]
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
363
        kind = ie.kind
364
        if kind == 'file':
0.99.8 by Ian Clatworthy
handle copy of a newly added file
365
            if newly_added:
366
                content = ''.join(self.lines_for_commit[file_id])
367
            else:
368
                content = self.rev_store.get_file_text(self.parents[0], file_id)
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
369
            self._modify_item(dest_path, kind, ie.executable, content, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
370
        elif kind == 'symlink':
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
371
            self._modify_item(dest_path, kind, False, ie.symlink_target, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
372
        else:
373
            self.warning("ignoring copy of %s %s - feature not yet supported",
374
                kind, path)
375
376
    def _rename_item(self, old_path, new_path, inv):
0.99.6 by Ian Clatworthy
Handle rename of a just added file
377
        existing = self._new_file_ids.get(old_path)
378
        if existing:
379
            # We've only just added this path earlier in this commit.
380
            # Change the add of old_path to an add of new_path
381
            self._rename_pending_add(old_path, new_path, existing)
382
            return
383
0.81.8 by Ian Clatworthy
refactor rename_item
384
        file_id = inv.path2id(old_path)
0.64.167 by Ian Clatworthy
incremental packing for chk formats
385
        if file_id is None:
386
            self.warning(
387
                "ignoring rename of %s to %s - old path does not exist" %
388
                (old_path, new_path))
389
            return
0.81.8 by Ian Clatworthy
refactor rename_item
390
        ie = inv[file_id]
391
        rev_id = ie.revision
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
392
        new_file_id = inv.path2id(new_path)
393
        if new_file_id is not None:
0.81.9 by Ian Clatworthy
refactor delete_item
394
            self.record_delete(new_path, inv[new_file_id])
0.81.8 by Ian Clatworthy
refactor rename_item
395
        self.record_rename(old_path, new_path, file_id, ie)
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
396
0.81.8 by Ian Clatworthy
refactor rename_item
397
        # The revision-id for this entry will be/has been updated and
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
398
        # that means the loader then needs to know what the "new" text is.
399
        # We therefore must go back to the revision store to get it.
0.81.8 by Ian Clatworthy
refactor rename_item
400
        lines = self.rev_store.get_file_lines(rev_id, file_id)
401
        self.lines_for_commit[file_id] = lines
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
402
403
    def _delete_all_items(self, inv):
404
        for name, root_item in inv.root.children.iteritems():
405
            inv.remove_recursive_id(root_item.file_id)
406
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
407
    def _warn_unless_in_merges(self, fileid, path):
408
        if len(self.parents) <= 1:
409
            return
410
        for parent in self.parents[1:]:
411
            if fileid in self.get_inventory(parent):
412
                return
413
        self.warning("ignoring delete of %s as not in parent inventories", path)
414
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
415
416
class InventoryCommitHandler(GenericCommitHandler):
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
417
    """A CommitHandler that builds and saves Inventory objects."""
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
418
419
    def pre_process_files(self):
420
        super(InventoryCommitHandler, self).pre_process_files()
421
0.64.159 by Ian Clatworthy
make the file-id cache optional and branch-ref aware
422
        # Seed the inventory from the previous one. Note that
423
        # the parent class version of pre_process_files() has
424
        # already set the right basis_inventory for this branch
425
        # but we need to copy it in order to mutate it safely
426
        # without corrupting the cached inventory value.
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
427
        if len(self.parents) == 0:
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
428
            self.inventory = self.basis_inventory
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
429
        else:
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
430
            self.inventory = copy_inventory(self.basis_inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
431
        self.inventory_root = self.inventory.root
432
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
433
        # directory-path -> inventory-entry for current inventory
434
        self.directory_entries = dict(self.inventory.directories())
435
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
436
        # Initialise the inventory revision info as required
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
437
        if self.rev_store.expects_rich_root():
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
438
            self.inventory.revision_id = self.revision_id
439
        else:
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
440
            # In this revision store, root entries have no knit or weave.
441
            # When serializing out to disk and back in, root.revision is
442
            # always the new revision_id.
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
443
            self.inventory.root.revision = self.revision_id
444
445
    def post_process_files(self):
446
        """Save the revision."""
447
        self.cache_mgr.inventories[self.revision_id] = self.inventory
0.85.2 by Ian Clatworthy
improve per-file graph generation
448
        self.rev_store.load(self.revision, self.inventory, None,
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
449
            lambda file_id: self._get_lines(file_id),
0.85.2 by Ian Clatworthy
improve per-file graph generation
450
            lambda file_id: self._get_per_file_parents(file_id),
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
451
            lambda revision_ids: self._get_inventories(revision_ids))
452
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
453
    def record_new(self, path, ie):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
454
        try:
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
455
            # If this is a merge, the file was most likely added already.
456
            # The per-file parent(s) must therefore be calculated and
457
            # we can't assume there are none.
458
            per_file_parents, ie.revision = \
459
                self.rev_store.get_parents_and_revision_for_entry(ie)
460
            self.per_file_parents_for_commit[ie.file_id] = per_file_parents
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
461
            self.inventory.add(ie)
462
        except errors.DuplicateFileId:
463
            # Directory already exists as a file or symlink
464
            del self.inventory[ie.file_id]
465
            # Try again
466
            self.inventory.add(ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
467
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
468
    def record_changed(self, path, ie, parent_id):
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
469
        # HACK: no API for this (del+add does more than it needs to)
0.85.2 by Ian Clatworthy
improve per-file graph generation
470
        per_file_parents, ie.revision = \
471
            self.rev_store.get_parents_and_revision_for_entry(ie)
472
        self.per_file_parents_for_commit[ie.file_id] = per_file_parents
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
473
        self.inventory._byid[ie.file_id] = ie
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
474
        parent_ie = self.inventory._byid[parent_id]
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
475
        parent_ie.children[ie.name] = ie
476
0.81.9 by Ian Clatworthy
refactor delete_item
477
    def record_delete(self, path, ie):
478
        self.inventory.remove_recursive_id(ie.file_id)
0.81.8 by Ian Clatworthy
refactor rename_item
479
480
    def record_rename(self, old_path, new_path, file_id, ie):
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
481
        # For a rename, the revision-id is always the new one so
482
        # no need to change/set it here
483
        ie.revision = self.revision_id
484
        per_file_parents, _ = \
485
            self.rev_store.get_parents_and_revision_for_entry(ie)
486
        self.per_file_parents_for_commit[file_id] = per_file_parents
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
487
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
488
            self.inventory)
0.81.8 by Ian Clatworthy
refactor rename_item
489
        self.inventory.rename(file_id, new_parent_id, new_basename)
490
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
491
    def modify_handler(self, filecmd):
492
        if filecmd.dataref is not None:
493
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
494
        else:
495
            data = filecmd.data
496
        self.debug("modifying %s", filecmd.path)
497
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
498
            filecmd.is_executable, data, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
499
500
    def delete_handler(self, filecmd):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
501
        self.debug("deleting %s", filecmd.path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
502
        self._delete_item(filecmd.path, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
503
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
504
    def copy_handler(self, filecmd):
505
        src_path = filecmd.src_path
506
        dest_path = filecmd.dest_path
507
        self.debug("copying %s to %s", src_path, dest_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
508
        self._copy_item(src_path, dest_path, self.inventory)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
509
510
    def rename_handler(self, filecmd):
511
        old_path = filecmd.old_path
512
        new_path = filecmd.new_path
513
        self.debug("renaming %s to %s", old_path, new_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
514
        self._rename_item(old_path, new_path, self.inventory)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
515
516
    def deleteall_handler(self, filecmd):
517
        self.debug("deleting all files (and also all directories)")
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
518
        self._delete_all_items(self.inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
519
520
0.64.171 by Ian Clatworthy
use inv deltas by default for all formats now: --classic to get old algorithm for packs
521
class InventoryDeltaCommitHandler(GenericCommitHandler):
522
    """A CommitHandler that builds Inventories by applying a delta."""
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
523
524
    def pre_process_files(self):
0.64.171 by Ian Clatworthy
use inv deltas by default for all formats now: --classic to get old algorithm for packs
525
        super(InventoryDeltaCommitHandler, self).pre_process_files()
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
526
        self._dirs_that_might_become_empty = set()
527
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
528
        # A given file-id can only appear once so we accumulate
529
        # the entries in a dict then build the actual delta at the end
530
        self._delta_entries_by_fileid = {}
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
531
        if len(self.parents) == 0 or not self.rev_store.expects_rich_root():
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
532
            if self.parents:
533
                old_path = ''
534
            else:
535
                old_path = None
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
536
            # Need to explicitly add the root entry for the first revision
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
537
            # and for non rich-root inventories
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
538
            root_id = inventory.ROOT_ID
539
            root_ie = inventory.InventoryDirectory(root_id, u'', None)
540
            root_ie.revision = self.revision_id
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
541
            self._add_entry((old_path, '', root_id, root_ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
542
543
    def post_process_files(self):
544
        """Save the revision."""
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
545
        delta = self._get_final_delta()
0.64.171 by Ian Clatworthy
use inv deltas by default for all formats now: --classic to get old algorithm for packs
546
        inv = self.rev_store.load_using_delta(self.revision,
547
            self.basis_inventory, delta, None,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
548
            lambda file_id: self._get_lines(file_id),
0.85.2 by Ian Clatworthy
improve per-file graph generation
549
            lambda file_id: self._get_per_file_parents(file_id),
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
550
            lambda revision_ids: self._get_inventories(revision_ids))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
551
        self.cache_mgr.inventories[self.revision_id] = inv
0.84.8 by Ian Clatworthy
ensure the chk stuff is only used on formats actually supporting it
552
        #print "committed %s" % self.revision_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
553
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
554
    def _get_final_delta(self):
555
        """Generate the final delta.
556
557
        Smart post-processing of changes, e.g. pruning of directories
558
        that would become empty, goes here.
559
        """
560
        delta = list(self._delta_entries_by_fileid.values())
561
        if self.prune_empty_dirs and self._dirs_that_might_become_empty:
562
            candidates = osutils.minimum_path_selection(
563
                self._dirs_that_might_become_empty)
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
564
            never_born = set()
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
565
            for path, file_id in self._empty_after_delta(delta, candidates):
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
566
                newly_added = self._new_file_ids.get(path)
567
                if newly_added:
568
                    never_born.add(newly_added)
569
                else:
570
                    delta.append((path, None, file_id, None))
571
            # Clean up entries that got deleted before they were ever added
572
            if never_born:
573
                delta = [de for de in delta if de[2] not in never_born]
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
574
        return delta
575
576
    def _empty_after_delta(self, delta, candidates):
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
577
        #self.mutter("delta so far is:\n%s" % "\n".join([str(de) for de in delta]))
578
        #self.mutter("candidates for deletion are:\n%s" % "\n".join([c for c in candidates]))
579
        new_inv = self._get_proposed_inventory(delta)
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
580
        result = []
581
        for dir in candidates:
582
            file_id = new_inv.path2id(dir)
0.64.219 by Ian Clatworthy
More robust implicit delete logic when file-id not found
583
            if file_id is None:
584
                continue
0.96.2 by Ian Clatworthy
test and fix for implicit directory delete recursing up
585
            ie = new_inv[file_id]
586
            if len(ie.children) == 0:
587
                result.append((dir, file_id))
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
588
                if self.verbose:
589
                    self.note("pruning empty directory %s" % (dir,))
0.96.2 by Ian Clatworthy
test and fix for implicit directory delete recursing up
590
                # Check parents in case deleting this dir makes *them* empty
591
                while True:
592
                    file_id = ie.parent_id
593
                    if file_id == inventory.ROOT_ID:
594
                        # We've reach the root
595
                        break
596
                    try:
597
                        ie = new_inv[file_id]
598
                    except errors.NoSuchId:
599
                        break
600
                    if len(ie.children) > 1:
601
                        break
602
                    dir = new_inv.id2path(file_id)
603
                    result.append((dir, file_id))
604
                    if self.verbose:
605
                        self.note("pruning empty directory parent %s" % (dir,))
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
606
        return result
607
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
608
    def _get_proposed_inventory(self, delta):
609
        if len(self.parents):
610
            new_inv = self.basis_inventory._get_mutable_inventory()
611
        else:
612
            new_inv = inventory.Inventory(revision_id=self.revision_id)
613
            # This is set in the delta so remove it to prevent a duplicate
614
            del new_inv[inventory.ROOT_ID]
0.99.9 by Ian Clatworthy
better diagnostics on inconsistent delta
615
        try:
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
616
            new_inv.apply_delta(delta)
0.99.9 by Ian Clatworthy
better diagnostics on inconsistent delta
617
        except errors.InconsistentDelta:
618
            self.mutter("INCONSISTENT DELTA IS:\n%s" % "\n".join([str(de) for de in delta]))
619
            raise
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
620
        return new_inv
621
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
622
    def _add_entry(self, entry):
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
623
        # We need to combine the data if multiple entries have the same file-id.
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
624
        # For example, a rename followed by a modification looks like:
625
        #
626
        # (x, y, f, e) & (y, y, f, g) => (x, y, f, g)
627
        #
628
        # Likewise, a modification followed by a rename looks like:
629
        #
630
        # (x, x, f, e) & (x, y, f, g) => (x, y, f, g)
631
        #
632
        # Here's a rename followed by a delete and a modification followed by
633
        # a delete:
634
        #
635
        # (x, y, f, e) & (y, None, f, None) => (x, None, f, None)
636
        # (x, x, f, e) & (x, None, f, None) => (x, None, f, None)
637
        #
638
        # In summary, we use the original old-path, new new-path and new ie
639
        # when combining entries.
0.85.2 by Ian Clatworthy
improve per-file graph generation
640
        old_path = entry[0]
641
        new_path = entry[1]
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
642
        file_id = entry[2]
0.85.2 by Ian Clatworthy
improve per-file graph generation
643
        ie = entry[3]
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
644
        existing = self._delta_entries_by_fileid.get(file_id, None)
645
        if existing is not None:
0.85.2 by Ian Clatworthy
improve per-file graph generation
646
            old_path = existing[0]
647
            entry = (old_path, new_path, file_id, ie)
0.99.6 by Ian Clatworthy
Handle rename of a just added file
648
        if new_path is None and old_path is None:
649
            # This is a delete cancelling a previous add
650
            del self._delta_entries_by_fileid[file_id]
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
651
            parent_dir = osutils.dirname(existing[1])
652
            self.mutter("cancelling add of %s with parent %s" % (existing[1], parent_dir))
653
            if parent_dir:
654
                self._dirs_that_might_become_empty.add(parent_dir)
0.99.6 by Ian Clatworthy
Handle rename of a just added file
655
            return
656
        else:
657
            self._delta_entries_by_fileid[file_id] = entry
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
658
0.99.6 by Ian Clatworthy
Handle rename of a just added file
659
        # Collect parent directories that might become empty
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
660
        if new_path is None:
661
            # delete
662
            parent_dir = osutils.dirname(old_path)
663
            # note: no need to check the root
664
            if parent_dir:
665
                self._dirs_that_might_become_empty.add(parent_dir)
666
        elif old_path is not None and old_path != new_path:
667
            # rename
668
            old_parent_dir = osutils.dirname(old_path)
669
            new_parent_dir = osutils.dirname(new_path)
670
            if old_parent_dir and old_parent_dir != new_parent_dir:
671
                self._dirs_that_might_become_empty.add(old_parent_dir)
672
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
673
        # Calculate the per-file parents, if not already done
674
        if file_id in self.per_file_parents_for_commit:
675
            return
0.85.2 by Ian Clatworthy
improve per-file graph generation
676
        if old_path is None:
677
            # add
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
678
            # If this is a merge, the file was most likely added already.
679
            # The per-file parent(s) must therefore be calculated and
680
            # we can't assume there are none.
681
            per_file_parents, ie.revision = \
682
                self.rev_store.get_parents_and_revision_for_entry(ie)
683
            self.per_file_parents_for_commit[file_id] = per_file_parents
0.85.2 by Ian Clatworthy
improve per-file graph generation
684
        elif new_path is None:
685
            # delete
686
            pass
687
        elif old_path != new_path:
688
            # rename
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
689
            per_file_parents, _ = \
690
                self.rev_store.get_parents_and_revision_for_entry(ie)
691
            self.per_file_parents_for_commit[file_id] = per_file_parents
0.85.2 by Ian Clatworthy
improve per-file graph generation
692
        else:
693
            # modify
694
            per_file_parents, ie.revision = \
695
                self.rev_store.get_parents_and_revision_for_entry(ie)
696
            self.per_file_parents_for_commit[file_id] = per_file_parents
697
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
698
    def record_new(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
699
        self._add_entry((None, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
700
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
701
    def record_changed(self, path, ie, parent_id=None):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
702
        self._add_entry((path, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
703
0.81.9 by Ian Clatworthy
refactor delete_item
704
    def record_delete(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
705
        self._add_entry((path, None, ie.file_id, None))
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
706
        self._paths_deleted_this_commit.add(path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
707
        if ie.kind == 'directory':
0.64.187 by Ian Clatworthy
fix inv-delta generation when deleting directories
708
            for child_relpath, entry in \
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
709
                self.basis_inventory.iter_entries_by_dir(from_dir=ie):
0.64.187 by Ian Clatworthy
fix inv-delta generation when deleting directories
710
                child_path = osutils.pathjoin(path, child_relpath)
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
711
                self._add_entry((child_path, None, entry.file_id, None))
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
712
                self._paths_deleted_this_commit.add(child_path)
0.81.8 by Ian Clatworthy
refactor rename_item
713
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
714
    def record_rename(self, old_path, new_path, file_id, old_ie):
715
        new_ie = old_ie.copy()
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
716
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
717
            self.basis_inventory)
718
        new_ie.name = new_basename
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
719
        new_ie.parent_id = new_parent_id
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
720
        new_ie.revision = self.revision_id
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
721
        self._add_entry((old_path, new_path, file_id, new_ie))
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
722
0.99.6 by Ian Clatworthy
Handle rename of a just added file
723
    def _rename_pending_add(self, old_path, new_path, file_id):
724
        """Instead of adding old-path, add new-path instead."""
725
        # note: delta entries look like (old, new, file-id, ie)
726
        old_ie = self._delta_entries_by_fileid[file_id][3]
727
728
        # Delete the old path. Note that this might trigger implicit
729
        # deletion of newly created parents that could now become empty.
730
        self.record_delete(old_path, old_ie)
731
732
        # Update the dictionary used for tracking new file-ids
733
        del self._new_file_ids[old_path]
734
        self._new_file_ids[new_path] = file_id
735
736
        # Create the new InventoryEntry
737
        kind = old_ie.kind
738
        basename, parent_id = self._ensure_directory(new_path,
739
            self.basis_inventory)
740
        ie = inventory.make_entry(kind, basename, parent_id, file_id)
741
        ie.revision = self.revision_id
742
        if kind == 'file':
743
            ie.executable = old_ie.executable
744
            ie.text_sha1 = old_ie.text_sha1
745
            ie.text_size = old_ie.text_size
746
        elif kind == 'symlink':
747
            ie.symlink_target = old_ie.symlink_target
748
749
        # Record it
750
        self.record_new(new_path, ie)
751
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
752
    def modify_handler(self, filecmd):
753
        if filecmd.dataref is not None:
754
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
755
        else:
756
            data = filecmd.data
757
        self.debug("modifying %s", filecmd.path)
758
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
759
            filecmd.is_executable, data, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
760
761
    def delete_handler(self, filecmd):
762
        self.debug("deleting %s", filecmd.path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
763
        self._delete_item(filecmd.path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
764
765
    def copy_handler(self, filecmd):
766
        src_path = filecmd.src_path
767
        dest_path = filecmd.dest_path
768
        self.debug("copying %s to %s", src_path, dest_path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
769
        self._copy_item(src_path, dest_path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
770
771
    def rename_handler(self, filecmd):
772
        old_path = filecmd.old_path
773
        new_path = filecmd.new_path
774
        self.debug("renaming %s to %s", old_path, new_path)
775
        self._rename_item(old_path, new_path, self.basis_inventory)
776
777
    def deleteall_handler(self, filecmd):
778
        self.debug("deleting all files (and also all directories)")
779
        # I'm not 100% sure this will work in the delta case.
780
        # But clearing out the basis inventory so that everything
781
        # is added sounds ok in theory ...
782
        # We grab a copy as the basis is likely to be cached and
783
        # we don't want to destroy the cached version
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
784
        self.basis_inventory = copy_inventory(self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
785
        self._delete_all_items(self.basis_inventory)