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