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