/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,
26
    )
27
from bzrlib.plugins.fastimport import helpers, processor
28
29
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
30
def copy_inventory(inv):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
31
    # This currently breaks revision-id matching
32
    #if hasattr(inv, "_get_mutable_inventory"):
33
    #    # TODO: Make this a public API on inventory
34
    #    return inv._get_mutable_inventory()
35
36
    # TODO: Shallow copy - deep inventory copying is expensive
37
    return inv.copy()
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
38
39
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
40
class GenericCommitHandler(processor.CommitHandler):
41
    """Base class for Bazaar CommitHandlers."""
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
42
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
43
    def __init__(self, command, cache_mgr, rev_store, verbose=False):
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
44
        super(GenericCommitHandler, self).__init__(command)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
45
        self.cache_mgr = cache_mgr
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
46
        self.rev_store = rev_store
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
47
        self.verbose = verbose
48
49
    def pre_process_files(self):
50
        """Prepare for committing."""
51
        self.revision_id = self.gen_revision_id()
52
        # cache of texts for this commit, indexed by file-id
53
        self.lines_for_commit = {}
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
54
        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
55
            self.lines_for_commit[inventory.ROOT_ID] = []
56
57
        # Track the heads and get the real parent list
58
        parents = self.cache_mgr.track_heads(self.command)
59
60
        # Convert the parent commit-ids to bzr revision-ids
61
        if parents:
62
            self.parents = [self.cache_mgr.revision_ids[p]
63
                for p in parents]
64
        else:
65
            self.parents = []
66
        self.debug("%s id: %s, parents: %s", self.command.id,
67
            self.revision_id, str(self.parents))
68
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
69
        # Keep the basis inventory. This needs to be treated as read-only.
70
        if len(self.parents) == 0:
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
71
            self.basis_inventory = self._init_inventory()
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
72
        else:
73
            self.basis_inventory = self.get_inventory(self.parents[0])
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
74
        if hasattr(self.basis_inventory, "root_id"):
75
            self.inventory_root_id = self.basis_inventory.root_id
76
        else:
77
            self.inventory_root_id = self.basis_inventory.root.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
78
79
        # directory-path -> inventory-entry for current inventory
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
80
        self.directory_entries = {}
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
81
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
82
    def _init_inventory(self):
83
        return self.rev_store.init_inventory(self.revision_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
84
85
    def get_inventory(self, revision_id):
86
        """Get the inventory for a revision id."""
87
        try:
88
            inv = self.cache_mgr.inventories[revision_id]
89
        except KeyError:
90
            if self.verbose:
91
                self.note("get_inventory cache miss for %s", revision_id)
92
            # Not cached so reconstruct from the RevisionStore
93
            inv = self.rev_store.get_inventory(revision_id)
94
            self.cache_mgr.inventories[revision_id] = inv
95
        return inv
96
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
97
    def _get_lines(self, file_id):
98
        """Get the lines for a file-id."""
99
        return self.lines_for_commit[file_id]
100
101
    def _get_inventories(self, revision_ids):
102
        """Get the inventories for revision-ids.
103
        
104
        This is a callback used by the RepositoryStore to
105
        speed up inventory reconstruction.
106
        """
107
        present = []
108
        inventories = []
109
        # If an inventory is in the cache, we assume it was
110
        # successfully loaded into the revision store
111
        for revision_id in revision_ids:
112
            try:
113
                inv = self.cache_mgr.inventories[revision_id]
114
                present.append(revision_id)
115
            except KeyError:
116
                if self.verbose:
117
                    self.note("get_inventories cache miss for %s", revision_id)
118
                # Not cached so reconstruct from the revision store
119
                try:
120
                    inv = self.get_inventory(revision_id)
121
                    present.append(revision_id)
122
                except:
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
123
                    inv = self._init_inventory()
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
124
                self.cache_mgr.inventories[revision_id] = inv
125
            inventories.append(inv)
126
        return present, inventories
127
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
128
    def bzr_file_id_and_new(self, path):
129
        """Get a Bazaar file identifier and new flag for a path.
130
        
131
        :return: file_id, is_new where
132
          is_new = True if the file_id is newly created
133
        """
134
        try:
135
            id = self.cache_mgr.file_ids[path]
136
            return id, False
137
        except KeyError:
138
            id = generate_ids.gen_file_id(path)
139
            self.cache_mgr.file_ids[path] = id
140
            self.debug("Generated new file id %s for '%s'", id, path)
141
            return id, True
142
143
    def bzr_file_id(self, path):
144
        """Get a Bazaar file identifier for a path."""
145
        return self.bzr_file_id_and_new(path)[0]
146
147
    def gen_revision_id(self):
148
        """Generate a revision id.
149
150
        Subclasses may override this to produce deterministic ids say.
151
        """
152
        committer = self.command.committer
153
        # Perhaps 'who' being the person running the import is ok? If so,
154
        # it might be a bit quicker and give slightly better compression?
155
        who = "%s <%s>" % (committer[0],committer[1])
156
        timestamp = committer[2]
157
        return generate_ids.gen_revision_id(who, timestamp)
158
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
159
    def build_revision(self):
160
        rev_props = {}
161
        committer = self.command.committer
162
        who = "%s <%s>" % (committer[0],committer[1])
163
        author = self.command.author
164
        if author is not None:
165
            author_id = "%s <%s>" % (author[0],author[1])
166
            if author_id != who:
167
                rev_props['author'] = author_id
168
        return revision.Revision(
169
           timestamp=committer[2],
170
           timezone=committer[3],
171
           committer=who,
172
           message=helpers.escape_commit_message(self.command.message),
173
           revision_id=self.revision_id,
174
           properties=rev_props,
175
           parent_ids=self.parents)
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
176
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
177
    def _modify_item(self, path, kind, is_executable, data, inv):
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
178
        """Add to or change an item in the inventory."""
179
        # Create the new InventoryEntry
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
180
        basename, parent_id = self._ensure_directory(path, inv)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
181
        file_id = self.bzr_file_id(path)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
182
        ie = inventory.make_entry(kind, basename, parent_id, file_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
183
        ie.revision = self.revision_id
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
184
        if kind == 'file':
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
185
            ie.executable = is_executable
186
            lines = osutils.split_lines(data)
187
            ie.text_sha1 = osutils.sha_strings(lines)
188
            ie.text_size = sum(map(len, lines))
189
            self.lines_for_commit[file_id] = lines
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
190
        elif kind == 'symlink':
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
191
            ie.symlink_target = data.encode('utf8')
192
            # There are no lines stored for a symlink so
193
            # make sure the cache used by get_lines knows that
194
            self.lines_for_commit[file_id] = []
195
        else:
196
            raise errors.BzrError("Cannot import items of kind '%s' yet" %
197
                (kind,))
198
        # Record it
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
199
        if file_id in inv:
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
200
            old_ie = inv[file_id]
201
            if old_ie.kind == 'directory':
202
                self.record_delete(path, old_ie)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
203
            self.record_changed(path, ie, parent_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
204
        else:
205
            self.record_new(path, ie)
206
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
207
    def _ensure_directory(self, path, inv):
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
208
        """Ensure that the containing directory exists for 'path'"""
209
        dirname, basename = osutils.split(path)
210
        if dirname == '':
211
            # the root node doesn't get updated
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
212
            return basename, self.inventory_root_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
213
        try:
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
214
            ie = self._get_directory_entry(inv, dirname)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
215
        except KeyError:
216
            # We will create this entry, since it doesn't exist
217
            pass
218
        else:
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
219
            return basename, ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
220
221
        # No directory existed, we will just create one, first, make sure
222
        # the parent exists
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
223
        dir_basename, parent_id = self._ensure_directory(dirname, inv)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
224
        dir_file_id = self.bzr_file_id(dirname)
225
        ie = inventory.entry_factory['directory'](dir_file_id,
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
226
            dir_basename, parent_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
227
        ie.revision = self.revision_id
228
        self.directory_entries[dirname] = ie
229
        # There are no lines stored for a directory so
230
        # make sure the cache used by get_lines knows that
231
        self.lines_for_commit[dir_file_id] = []
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
232
233
        # It's possible that a file or symlink with that file-id
234
        # already exists. If it does, we need to delete it.
235
        if dir_file_id in inv:
236
            self.record_delete(dirname, ie)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
237
        self.record_new(dirname, ie)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
238
        return basename, ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
239
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
240
    def _get_directory_entry(self, inv, dirname):
241
        """Get the inventory entry for a directory.
242
        
243
        Raises KeyError if dirname is not a directory in inv.
244
        """
245
        result = self.directory_entries.get(dirname)
246
        if result is None:
247
            file_id = inv.path2id(dirname)
248
            if file_id is None:
249
                raise KeyError
250
            result = inv[file_id]
251
            # dirname must be a directory for us to return it
252
            if result.kind == 'directory':
253
                self.directory_entries[dirname] = result
254
            else:
255
                raise KeyError
256
        return result
257
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
258
    def _delete_item(self, path, inv):
259
        file_id = inv.path2id(path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
260
        #print "**** deleting %s with file-id %s" % (path, file_id)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
261
        ie = inv[file_id]
0.81.9 by Ian Clatworthy
refactor delete_item
262
        self.record_delete(path, ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
263
264
    def _copy_item(self, src_path, dest_path, inv):
265
        if not self.parents:
266
            self.warning("ignoring copy of %s to %s - no parent revisions",
267
                src_path, dest_path)
268
            return
269
        file_id = inv.path2id(src_path)
270
        if file_id is None:
271
            self.warning("ignoring copy of %s to %s - source does not exist",
272
                src_path, dest_path)
273
            return
274
        ie = inv[file_id]
275
        kind = ie.kind
276
        if kind == 'file':
277
            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
278
            self._modify_item(dest_path, kind, ie.executable, content, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
279
        elif kind == 'symlink':
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
280
            self._modify_item(dest_path, kind, False, ie.symlink_target, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
281
        else:
282
            self.warning("ignoring copy of %s %s - feature not yet supported",
283
                kind, path)
284
285
    def _rename_item(self, old_path, new_path, inv):
0.81.8 by Ian Clatworthy
refactor rename_item
286
        file_id = inv.path2id(old_path)
287
        ie = inv[file_id]
288
        rev_id = ie.revision
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
289
        new_file_id = inv.path2id(new_path)
290
        if new_file_id is not None:
0.81.9 by Ian Clatworthy
refactor delete_item
291
            self.record_delete(new_path, inv[new_file_id])
0.81.8 by Ian Clatworthy
refactor rename_item
292
        self.record_rename(old_path, new_path, file_id, ie)
293
        self.cache_mgr.rename_path(old_path, new_path)
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
294
0.81.8 by Ian Clatworthy
refactor rename_item
295
        # 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
296
        # that means the loader then needs to know what the "new" text is.
297
        # We therefore must go back to the revision store to get it.
0.81.8 by Ian Clatworthy
refactor rename_item
298
        lines = self.rev_store.get_file_lines(rev_id, file_id)
299
        self.lines_for_commit[file_id] = lines
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
300
301
    def _delete_all_items(self, inv):
302
        for name, root_item in inv.root.children.iteritems():
303
            inv.remove_recursive_id(root_item.file_id)
304
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
305
306
class InventoryCommitHandler(GenericCommitHandler):
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
307
    """A CommitHandler that builds and saves Inventory objects."""
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
308
309
    def pre_process_files(self):
310
        super(InventoryCommitHandler, self).pre_process_files()
311
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
312
        # Seed the inventory from the previous one
313
        if len(self.parents) == 0:
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
314
            self.inventory = self.basis_inventory
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
315
        else:
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
316
            self.inventory = copy_inventory(self.basis_inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
317
        self.inventory_root = self.inventory.root
318
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
319
        # directory-path -> inventory-entry for current inventory
320
        self.directory_entries = dict(self.inventory.directories())
321
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
322
        # Initialise the inventory revision info as required
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
323
        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
324
            self.inventory.revision_id = self.revision_id
325
        else:
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
326
            # In this revision store, root entries have no knit or weave.
327
            # When serializing out to disk and back in, root.revision is
328
            # always the new revision_id.
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
329
            self.inventory.root.revision = self.revision_id
330
331
    def post_process_files(self):
332
        """Save the revision."""
333
        self.cache_mgr.inventories[self.revision_id] = self.inventory
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
334
        rev = self.build_revision()
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
335
        self.rev_store.load(rev, self.inventory, None,
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
336
            lambda file_id: self._get_lines(file_id),
337
            lambda revision_ids: self._get_inventories(revision_ids))
338
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
339
    def record_new(self, path, ie):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
340
        try:
341
            self.inventory.add(ie)
342
        except errors.DuplicateFileId:
343
            # Directory already exists as a file or symlink
344
            del self.inventory[ie.file_id]
345
            # Try again
346
            self.inventory.add(ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
347
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
348
    def record_changed(self, path, ie, parent_id):
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
349
        # HACK: no API for this (del+add does more than it needs to)
350
        self.inventory._byid[ie.file_id] = ie
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
351
        parent_ie = self.inventory._byid[parent_id]
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
352
        parent_ie.children[ie.name] = ie
353
0.81.9 by Ian Clatworthy
refactor delete_item
354
    def record_delete(self, path, ie):
355
        self.inventory.remove_recursive_id(ie.file_id)
0.81.8 by Ian Clatworthy
refactor rename_item
356
357
    def record_rename(self, old_path, new_path, file_id, ie):
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
358
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
359
            self.inventory)
0.81.8 by Ian Clatworthy
refactor rename_item
360
        self.inventory.rename(file_id, new_parent_id, new_basename)
361
        self.inventory[file_id].revision = self.revision_id
362
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
363
    def _delete_item(self, path, inv):
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
364
        # NOTE: I'm retaining this method for now, instead of using the
365
        # one in the superclass, because it's taken quite a lot of tweaking
366
        # to cover all the edge cases seen in the wild. Long term, it can
367
        # probably go once the higher level method does "warn_unless_in_merges"
368
        # and handles all the various special cases ...
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
369
        fileid = self.bzr_file_id(path)
370
        dirname, basename = osutils.split(path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
371
        if (fileid in inv and
372
            isinstance(inv[fileid], inventory.InventoryDirectory)):
373
            for child_path in inv[fileid].children.keys():
374
                self._delete_item(osutils.pathjoin(path, child_path), inv)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
375
        try:
376
            if self.inventory.id2path(fileid) == path:
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
377
                del inv[fileid]
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
378
            else:
379
                # already added by some other name?
380
                if dirname in self.cache_mgr.file_ids:
381
                    parent_id = self.cache_mgr.file_ids[dirname]
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
382
                    del inv[parent_id].children[basename]
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
383
        except KeyError:
384
            self._warn_unless_in_merges(fileid, path)
385
        except errors.NoSuchId:
386
            self._warn_unless_in_merges(fileid, path)
387
        except AttributeError, ex:
388
            if ex.args[0] == 'children':
389
                # A directory has changed into a file and then one
390
                # of it's children is being deleted!
391
                self._warn_unless_in_merges(fileid, path)
392
            else:
393
                raise
394
        try:
395
            self.cache_mgr.delete_path(path)
396
        except KeyError:
397
            pass
398
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
399
    def _warn_unless_in_merges(self, fileid, path):
400
        if len(self.parents) <= 1:
401
            return
402
        for parent in self.parents[1:]:
403
            if fileid in self.get_inventory(parent):
404
                return
405
        self.warning("ignoring delete of %s as not in parent inventories", path)
406
407
    def modify_handler(self, filecmd):
408
        if filecmd.dataref is not None:
409
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
410
        else:
411
            data = filecmd.data
412
        self.debug("modifying %s", filecmd.path)
413
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
414
            filecmd.is_executable, data, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
415
416
    def delete_handler(self, filecmd):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
417
        self.debug("deleting %s", filecmd.path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
418
        self._delete_item(filecmd.path, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
419
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
420
    def copy_handler(self, filecmd):
421
        src_path = filecmd.src_path
422
        dest_path = filecmd.dest_path
423
        self.debug("copying %s to %s", src_path, dest_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
424
        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
425
426
    def rename_handler(self, filecmd):
427
        old_path = filecmd.old_path
428
        new_path = filecmd.new_path
429
        self.debug("renaming %s to %s", old_path, new_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
430
        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
431
432
    def deleteall_handler(self, filecmd):
433
        self.debug("deleting all files (and also all directories)")
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
434
        self._delete_all_items(self.inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
435
436
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
437
class CHKInventoryCommitHandler(GenericCommitHandler):
438
    """A CommitHandler that builds and saves CHKInventory objects."""
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
439
440
    def pre_process_files(self):
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
441
        super(CHKInventoryCommitHandler, self).pre_process_files()
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
442
        # A given file-id can only appear once so we accumulate
443
        # the entries in a dict then build the actual delta at the end
444
        self._delta_entries_by_fileid = {}
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
445
        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
446
            if self.parents:
447
                old_path = ''
448
            else:
449
                old_path = None
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
450
            # 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
451
            # and for non rich-root inventories
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
452
            root_id = inventory.ROOT_ID
453
            # XXX: We *could* make this a CHKInventoryDirectory but it
454
            # seems that deltas ought to use normal InventoryDirectory's
455
            # because they simply don't know the chk_inventory that they
456
            # are about to become a part of.
457
            root_ie = inventory.InventoryDirectory(root_id, u'', None)
458
            root_ie.revision = self.revision_id
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
459
            self._add_entry((old_path, '', root_id, root_ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
460
461
    def post_process_files(self):
462
        """Save the revision."""
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
463
        delta = list(self._delta_entries_by_fileid.values())
464
        #print "delta:\n%s\n\n" % "\n".join([str(de) for de in delta])
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
465
        rev = self.build_revision()
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
466
        inv = self.rev_store.chk_load(rev, self.basis_inventory, delta, None,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
467
            lambda file_id: self._get_lines(file_id),
468
            lambda revision_ids: self._get_inventories(revision_ids))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
469
        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
470
        #print "committed %s" % self.revision_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
471
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
472
    def _add_entry(self, entry):
473
        # We need to combine the data if multiple entries have the same fileid.
474
        # For example, a rename followed by a modification looks like:
475
        #
476
        # (x, y, f, e) & (y, y, f, g) => (x, y, f, g)
477
        #
478
        # Likewise, a modification followed by a rename looks like:
479
        #
480
        # (x, x, f, e) & (x, y, f, g) => (x, y, f, g)
481
        #
482
        # Here's a rename followed by a delete and a modification followed by
483
        # a delete:
484
        #
485
        # (x, y, f, e) & (y, None, f, None) => (x, None, f, None)
486
        # (x, x, f, e) & (x, None, f, None) => (x, None, f, None)
487
        #
488
        # In summary, we use the original old-path, new new-path and new ie
489
        # when combining entries.
490
        file_id = entry[2]
491
        existing = self._delta_entries_by_fileid.get(file_id, None)
492
        if existing is not None:
493
            entry = (existing[0], entry[1], file_id, entry[3])
494
        self._delta_entries_by_fileid[file_id] = entry
495
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
496
    def record_new(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
497
        self._add_entry((None, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
498
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
499
    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
500
        self._add_entry((path, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
501
0.81.9 by Ian Clatworthy
refactor delete_item
502
    def record_delete(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
503
        self._add_entry((path, None, ie.file_id, None))
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
504
        if ie.kind == 'directory':
505
            for child_path, entry in \
506
                self.basis_inventory.iter_entries_by_dir(from_dir=ie):
507
                #print "deleting child %s" % child_path
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
508
                self._add_entry((child_path, None, entry.file_id, None))
0.81.8 by Ian Clatworthy
refactor rename_item
509
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
510
    def record_rename(self, old_path, new_path, file_id, old_ie):
511
        new_ie = old_ie.copy()
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
512
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
513
            self.basis_inventory)
514
        new_ie.name = new_basename
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
515
        new_ie.parent_id = new_parent_id
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
516
        new_ie.revision = self.revision_id
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
517
        self._add_entry((old_path, new_path, file_id, new_ie))
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
518
519
    def modify_handler(self, filecmd):
520
        if filecmd.dataref is not None:
521
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
522
        else:
523
            data = filecmd.data
524
        self.debug("modifying %s", filecmd.path)
525
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
526
            filecmd.is_executable, data, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
527
528
    def delete_handler(self, filecmd):
529
        self.debug("deleting %s", filecmd.path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
530
        self._delete_item(filecmd.path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
531
532
    def copy_handler(self, filecmd):
533
        src_path = filecmd.src_path
534
        dest_path = filecmd.dest_path
535
        self.debug("copying %s to %s", src_path, dest_path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
536
        self._copy_item(src_path, dest_path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
537
538
    def rename_handler(self, filecmd):
539
        old_path = filecmd.old_path
540
        new_path = filecmd.new_path
541
        self.debug("renaming %s to %s", old_path, new_path)
542
        self._rename_item(old_path, new_path, self.basis_inventory)
543
544
    def deleteall_handler(self, filecmd):
545
        self.debug("deleting all files (and also all directories)")
546
        # I'm not 100% sure this will work in the delta case.
547
        # But clearing out the basis inventory so that everything
548
        # is added sounds ok in theory ...
549
        # We grab a copy as the basis is likely to be cached and
550
        # we don't want to destroy the cached version
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
551
        self.basis_inventory = copy_inventory(self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
552
        self._delete_all_items(self.basis_inventory)