/brz/remove-bazaar

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