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