/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:
0.64.148 by Ian Clatworthy
handle delete of unknown file in chk formats & reduce noise
91
                self.mutter("get_inventory cache miss for %s", revision_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
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:
0.64.146 by Ian Clatworthy
fix first file is in a subdirectory bug for chk formats
247
            try:
248
                file_id = inv.path2id(dirname)
249
            except errors.NoSuchId:
250
                # In a CHKInventory, this is raised if there's no root yet
251
                raise KeyError
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
252
            if file_id is None:
253
                raise KeyError
254
            result = inv[file_id]
255
            # dirname must be a directory for us to return it
256
            if result.kind == 'directory':
257
                self.directory_entries[dirname] = result
258
            else:
259
                raise KeyError
260
        return result
261
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
262
    def _delete_item(self, path, inv):
263
        file_id = inv.path2id(path)
0.64.148 by Ian Clatworthy
handle delete of unknown file in chk formats & reduce noise
264
        if file_id is None:
265
            self.mutter("ignoring delete of %s as not in inventory", path)
266
            return
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
267
        try:
268
            ie = inv[file_id]
269
        except errors.NoSuchId:
0.64.148 by Ian Clatworthy
handle delete of unknown file in chk formats & reduce noise
270
            self.mutter("ignoring delete of %s as not in inventory", path)
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
271
        else:
272
            self.record_delete(path, ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
273
274
    def _copy_item(self, src_path, dest_path, inv):
275
        if not self.parents:
276
            self.warning("ignoring copy of %s to %s - no parent revisions",
277
                src_path, dest_path)
278
            return
279
        file_id = inv.path2id(src_path)
280
        if file_id is None:
281
            self.warning("ignoring copy of %s to %s - source does not exist",
282
                src_path, dest_path)
283
            return
284
        ie = inv[file_id]
285
        kind = ie.kind
286
        if kind == 'file':
287
            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
288
            self._modify_item(dest_path, kind, ie.executable, content, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
289
        elif kind == 'symlink':
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
290
            self._modify_item(dest_path, kind, False, ie.symlink_target, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
291
        else:
292
            self.warning("ignoring copy of %s %s - feature not yet supported",
293
                kind, path)
294
295
    def _rename_item(self, old_path, new_path, inv):
0.81.8 by Ian Clatworthy
refactor rename_item
296
        file_id = inv.path2id(old_path)
297
        ie = inv[file_id]
298
        rev_id = ie.revision
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
299
        new_file_id = inv.path2id(new_path)
300
        if new_file_id is not None:
0.81.9 by Ian Clatworthy
refactor delete_item
301
            self.record_delete(new_path, inv[new_file_id])
0.81.8 by Ian Clatworthy
refactor rename_item
302
        self.record_rename(old_path, new_path, file_id, ie)
303
        self.cache_mgr.rename_path(old_path, new_path)
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
304
0.81.8 by Ian Clatworthy
refactor rename_item
305
        # 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
306
        # that means the loader then needs to know what the "new" text is.
307
        # We therefore must go back to the revision store to get it.
0.81.8 by Ian Clatworthy
refactor rename_item
308
        lines = self.rev_store.get_file_lines(rev_id, file_id)
309
        self.lines_for_commit[file_id] = lines
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
310
311
    def _delete_all_items(self, inv):
312
        for name, root_item in inv.root.children.iteritems():
313
            inv.remove_recursive_id(root_item.file_id)
314
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
315
    def _warn_unless_in_merges(self, fileid, path):
316
        if len(self.parents) <= 1:
317
            return
318
        for parent in self.parents[1:]:
319
            if fileid in self.get_inventory(parent):
320
                return
321
        self.warning("ignoring delete of %s as not in parent inventories", path)
322
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
323
324
class InventoryCommitHandler(GenericCommitHandler):
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
325
    """A CommitHandler that builds and saves Inventory objects."""
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
326
327
    def pre_process_files(self):
328
        super(InventoryCommitHandler, self).pre_process_files()
329
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
330
        # Seed the inventory from the previous one
331
        if len(self.parents) == 0:
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
332
            self.inventory = self.basis_inventory
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
333
        else:
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
334
            self.inventory = copy_inventory(self.basis_inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
335
        self.inventory_root = self.inventory.root
336
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
337
        # directory-path -> inventory-entry for current inventory
338
        self.directory_entries = dict(self.inventory.directories())
339
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
340
        # Initialise the inventory revision info as required
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
341
        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
342
            self.inventory.revision_id = self.revision_id
343
        else:
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
344
            # In this revision store, root entries have no knit or weave.
345
            # When serializing out to disk and back in, root.revision is
346
            # always the new revision_id.
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
347
            self.inventory.root.revision = self.revision_id
348
349
    def post_process_files(self):
350
        """Save the revision."""
351
        self.cache_mgr.inventories[self.revision_id] = self.inventory
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
352
        rev = self.build_revision()
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
353
        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
354
            lambda file_id: self._get_lines(file_id),
355
            lambda revision_ids: self._get_inventories(revision_ids))
356
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
357
    def record_new(self, path, ie):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
358
        try:
359
            self.inventory.add(ie)
360
        except errors.DuplicateFileId:
361
            # Directory already exists as a file or symlink
362
            del self.inventory[ie.file_id]
363
            # Try again
364
            self.inventory.add(ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
365
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
366
    def record_changed(self, path, ie, parent_id):
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
367
        # HACK: no API for this (del+add does more than it needs to)
368
        self.inventory._byid[ie.file_id] = ie
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
369
        parent_ie = self.inventory._byid[parent_id]
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
370
        parent_ie.children[ie.name] = ie
371
0.81.9 by Ian Clatworthy
refactor delete_item
372
    def record_delete(self, path, ie):
373
        self.inventory.remove_recursive_id(ie.file_id)
0.81.8 by Ian Clatworthy
refactor rename_item
374
375
    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
376
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
377
            self.inventory)
0.81.8 by Ian Clatworthy
refactor rename_item
378
        self.inventory.rename(file_id, new_parent_id, new_basename)
379
        self.inventory[file_id].revision = self.revision_id
380
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
381
    def _delete_item(self, path, inv):
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
382
        # NOTE: I'm retaining this method for now, instead of using the
383
        # one in the superclass, because it's taken quite a lot of tweaking
384
        # to cover all the edge cases seen in the wild. Long term, it can
385
        # probably go once the higher level method does "warn_unless_in_merges"
386
        # 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
387
        fileid = self.bzr_file_id(path)
388
        dirname, basename = osutils.split(path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
389
        if (fileid in inv and
390
            isinstance(inv[fileid], inventory.InventoryDirectory)):
391
            for child_path in inv[fileid].children.keys():
392
                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
393
        try:
394
            if self.inventory.id2path(fileid) == path:
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
395
                del inv[fileid]
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
396
            else:
397
                # already added by some other name?
398
                if dirname in self.cache_mgr.file_ids:
399
                    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
400
                    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
401
        except KeyError:
402
            self._warn_unless_in_merges(fileid, path)
403
        except errors.NoSuchId:
404
            self._warn_unless_in_merges(fileid, path)
405
        except AttributeError, ex:
406
            if ex.args[0] == 'children':
407
                # A directory has changed into a file and then one
408
                # of it's children is being deleted!
409
                self._warn_unless_in_merges(fileid, path)
410
            else:
411
                raise
412
        try:
413
            self.cache_mgr.delete_path(path)
414
        except KeyError:
415
            pass
416
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
417
    def modify_handler(self, filecmd):
418
        if filecmd.dataref is not None:
419
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
420
        else:
421
            data = filecmd.data
422
        self.debug("modifying %s", filecmd.path)
423
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
424
            filecmd.is_executable, data, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
425
426
    def delete_handler(self, filecmd):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
427
        self.debug("deleting %s", filecmd.path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
428
        self._delete_item(filecmd.path, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
429
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
430
    def copy_handler(self, filecmd):
431
        src_path = filecmd.src_path
432
        dest_path = filecmd.dest_path
433
        self.debug("copying %s to %s", src_path, dest_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
434
        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
435
436
    def rename_handler(self, filecmd):
437
        old_path = filecmd.old_path
438
        new_path = filecmd.new_path
439
        self.debug("renaming %s to %s", old_path, new_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
440
        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
441
442
    def deleteall_handler(self, filecmd):
443
        self.debug("deleting all files (and also all directories)")
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
444
        self._delete_all_items(self.inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
445
446
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
447
class CHKInventoryCommitHandler(GenericCommitHandler):
448
    """A CommitHandler that builds and saves CHKInventory objects."""
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
449
450
    def pre_process_files(self):
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
451
        super(CHKInventoryCommitHandler, self).pre_process_files()
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
452
        # A given file-id can only appear once so we accumulate
453
        # the entries in a dict then build the actual delta at the end
454
        self._delta_entries_by_fileid = {}
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
455
        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
456
            if self.parents:
457
                old_path = ''
458
            else:
459
                old_path = None
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
460
            # 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
461
            # and for non rich-root inventories
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
462
            root_id = inventory.ROOT_ID
463
            # XXX: We *could* make this a CHKInventoryDirectory but it
464
            # seems that deltas ought to use normal InventoryDirectory's
465
            # because they simply don't know the chk_inventory that they
466
            # are about to become a part of.
467
            root_ie = inventory.InventoryDirectory(root_id, u'', None)
468
            root_ie.revision = self.revision_id
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
469
            self._add_entry((old_path, '', root_id, root_ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
470
471
    def post_process_files(self):
472
        """Save the revision."""
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
473
        delta = list(self._delta_entries_by_fileid.values())
474
        #print "delta:\n%s\n\n" % "\n".join([str(de) for de in delta])
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
475
        rev = self.build_revision()
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
476
        inv = self.rev_store.chk_load(rev, self.basis_inventory, delta, None,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
477
            lambda file_id: self._get_lines(file_id),
478
            lambda revision_ids: self._get_inventories(revision_ids))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
479
        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
480
        #print "committed %s" % self.revision_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
481
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
482
    def _add_entry(self, entry):
483
        # We need to combine the data if multiple entries have the same fileid.
484
        # For example, a rename followed by a modification looks like:
485
        #
486
        # (x, y, f, e) & (y, y, f, g) => (x, y, f, g)
487
        #
488
        # Likewise, a modification followed by a rename looks like:
489
        #
490
        # (x, x, f, e) & (x, y, f, g) => (x, y, f, g)
491
        #
492
        # Here's a rename followed by a delete and a modification followed by
493
        # a delete:
494
        #
495
        # (x, y, f, e) & (y, None, f, None) => (x, None, f, None)
496
        # (x, x, f, e) & (x, None, f, None) => (x, None, f, None)
497
        #
498
        # In summary, we use the original old-path, new new-path and new ie
499
        # when combining entries.
500
        file_id = entry[2]
501
        existing = self._delta_entries_by_fileid.get(file_id, None)
502
        if existing is not None:
503
            entry = (existing[0], entry[1], file_id, entry[3])
504
        self._delta_entries_by_fileid[file_id] = entry
505
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
506
    def record_new(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
507
        self._add_entry((None, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
508
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
509
    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
510
        self._add_entry((path, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
511
0.81.9 by Ian Clatworthy
refactor delete_item
512
    def record_delete(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
513
        self._add_entry((path, None, ie.file_id, None))
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
514
        if ie.kind == 'directory':
515
            for child_path, entry in \
516
                self.basis_inventory.iter_entries_by_dir(from_dir=ie):
517
                #print "deleting child %s" % child_path
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
518
                self._add_entry((child_path, None, entry.file_id, None))
0.81.8 by Ian Clatworthy
refactor rename_item
519
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
520
    def record_rename(self, old_path, new_path, file_id, old_ie):
521
        new_ie = old_ie.copy()
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
522
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
523
            self.basis_inventory)
524
        new_ie.name = new_basename
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
525
        new_ie.parent_id = new_parent_id
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
526
        new_ie.revision = self.revision_id
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
527
        self._add_entry((old_path, new_path, file_id, new_ie))
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
528
529
    def modify_handler(self, filecmd):
530
        if filecmd.dataref is not None:
531
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
532
        else:
533
            data = filecmd.data
534
        self.debug("modifying %s", filecmd.path)
535
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
536
            filecmd.is_executable, data, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
537
538
    def delete_handler(self, filecmd):
539
        self.debug("deleting %s", filecmd.path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
540
        self._delete_item(filecmd.path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
541
542
    def copy_handler(self, filecmd):
543
        src_path = filecmd.src_path
544
        dest_path = filecmd.dest_path
545
        self.debug("copying %s to %s", src_path, dest_path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
546
        self._copy_item(src_path, dest_path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
547
548
    def rename_handler(self, filecmd):
549
        old_path = filecmd.old_path
550
        new_path = filecmd.new_path
551
        self.debug("renaming %s to %s", old_path, new_path)
552
        self._rename_item(old_path, new_path, self.basis_inventory)
553
554
    def deleteall_handler(self, filecmd):
555
        self.debug("deleting all files (and also all directories)")
556
        # I'm not 100% sure this will work in the delta case.
557
        # But clearing out the basis inventory so that everything
558
        # is added sounds ok in theory ...
559
        # We grab a copy as the basis is likely to be cached and
560
        # we don't want to destroy the cached version
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
561
        self.basis_inventory = copy_inventory(self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
562
        self._delete_all_items(self.basis_inventory)