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