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