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