/brz/remove-bazaar

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