/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.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
30
class GenericCommitHandler(processor.CommitHandler):
31
    """Base class for Bazaar CommitHandlers."""
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
32
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
33
    def __init__(self, command, cache_mgr, rev_store, verbose=False):
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
34
        super(GenericCommitHandler, self).__init__(command)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
35
        self.cache_mgr = cache_mgr
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
36
        self.rev_store = rev_store
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
37
        self.verbose = verbose
38
39
    def pre_process_files(self):
40
        """Prepare for committing."""
41
        self.revision_id = self.gen_revision_id()
42
        # cache of texts for this commit, indexed by file-id
43
        self.lines_for_commit = {}
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
44
        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
45
            self.lines_for_commit[inventory.ROOT_ID] = []
46
47
        # Track the heads and get the real parent list
48
        parents = self.cache_mgr.track_heads(self.command)
49
50
        # Convert the parent commit-ids to bzr revision-ids
51
        if parents:
52
            self.parents = [self.cache_mgr.revision_ids[p]
53
                for p in parents]
54
        else:
55
            self.parents = []
56
        self.debug("%s id: %s, parents: %s", self.command.id,
57
            self.revision_id, str(self.parents))
58
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
59
    def build_revision(self):
60
        rev_props = {}
61
        committer = self.command.committer
62
        who = "%s <%s>" % (committer[0],committer[1])
63
        author = self.command.author
64
        if author is not None:
65
            author_id = "%s <%s>" % (author[0],author[1])
66
            if author_id != who:
67
                rev_props['author'] = author_id
68
        return revision.Revision(
69
           timestamp=committer[2],
70
           timezone=committer[3],
71
           committer=who,
72
           message=helpers.escape_commit_message(self.command.message),
73
           revision_id=self.revision_id,
74
           properties=rev_props,
75
           parent_ids=self.parents)
76
77
    def bzr_file_id_and_new(self, path):
78
        """Get a Bazaar file identifier and new flag for a path.
79
        
80
        :return: file_id, is_new where
81
          is_new = True if the file_id is newly created
82
        """
83
        try:
84
            id = self.cache_mgr.file_ids[path]
85
            return id, False
86
        except KeyError:
87
            id = generate_ids.gen_file_id(path)
88
            self.cache_mgr.file_ids[path] = id
89
            self.debug("Generated new file id %s for '%s'", id, path)
90
            return id, True
91
92
    def bzr_file_id(self, path):
93
        """Get a Bazaar file identifier for a path."""
94
        return self.bzr_file_id_and_new(path)[0]
95
96
    def gen_revision_id(self):
97
        """Generate a revision id.
98
99
        Subclasses may override this to produce deterministic ids say.
100
        """
101
        committer = self.command.committer
102
        # Perhaps 'who' being the person running the import is ok? If so,
103
        # it might be a bit quicker and give slightly better compression?
104
        who = "%s <%s>" % (committer[0],committer[1])
105
        timestamp = committer[2]
106
        return generate_ids.gen_revision_id(who, timestamp)
107
108
    def get_inventory(self, revision_id):
109
        """Get the inventory for a revision id."""
110
        try:
111
            inv = self.cache_mgr.inventories[revision_id]
112
        except KeyError:
113
            if self.verbose:
114
                self.note("get_inventory cache miss for %s", revision_id)
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
115
            # Not cached so reconstruct from the RevisionStore
116
            inv = self.rev_store.get_inventory(revision_id)
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
117
            self.cache_mgr.inventories[revision_id] = inv
118
        return inv
119
120
    def _warn_unless_in_merges(self, fileid, path):
121
        if len(self.parents) <= 1:
122
            return
123
        for parent in self.parents[1:]:
124
            if fileid in self.get_inventory(parent):
125
                return
126
        self.warning("ignoring delete of %s as not in parent inventories", path)
127
128
129
class InventoryCommitHandler(GenericCommitHandler):
0.81.3 by Ian Clatworthy
enhance RevisionLoader to try inventory deltas & decide on rich-roots
130
    """A CommitHandler that builds and saves full inventories."""
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
131
132
    def pre_process_files(self):
133
        super(InventoryCommitHandler, self).pre_process_files()
134
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
135
        # Seed the inventory from the previous one
136
        if len(self.parents) == 0:
137
            self.inventory = self.gen_initial_inventory()
138
        else:
139
            # use the bzr_revision_id to lookup the inv cache
140
            inv = self.get_inventory(self.parents[0])
141
            # TODO: Shallow copy - deep inventory copying is expensive
142
            self.inventory = inv.copy()
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
143
        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
144
            self.inventory.revision_id = self.revision_id
145
        else:
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
146
            # In this revision store, root entries have no knit or weave.
147
            # When serializing out to disk and back in, root.revision is
148
            # always the new revision_id.
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
149
            self.inventory.root.revision = self.revision_id
150
151
        # directory-path -> inventory-entry for current inventory
152
        self.directory_entries = dict(self.inventory.directories())
153
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
154
    def gen_initial_inventory(self):
155
        """Generate an inventory for a parentless revision."""
156
        inv = inventory.Inventory(revision_id=self.revision_id)
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
157
        if self.rev_store.expects_rich_root():
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
158
            # The very first root needs to have the right revision
159
            inv.root.revision = self.revision_id
160
        return inv
161
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
162
    def post_process_files(self):
163
        """Save the revision."""
164
        self.cache_mgr.inventories[self.revision_id] = self.inventory
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
165
        rev = self.build_revision()
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
166
        self.rev_store.load(rev, self.inventory, None,
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
167
            lambda file_id: self._get_lines(file_id),
168
            lambda revision_ids: self._get_inventories(revision_ids))
169
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
170
    def _get_lines(self, file_id):
171
        """Get the lines for a file-id."""
172
        return self.lines_for_commit[file_id]
173
174
    def _get_inventories(self, revision_ids):
175
        """Get the inventories for revision-ids.
176
        
177
        This is a callback used by the RepositoryLoader to
178
        speed up inventory reconstruction.
179
        """
180
        present = []
181
        inventories = []
182
        # If an inventory is in the cache, we assume it was
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
183
        # successfully loaded into the revision store
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
184
        for revision_id in revision_ids:
185
            try:
186
                inv = self.cache_mgr.inventories[revision_id]
187
                present.append(revision_id)
188
            except KeyError:
189
                if self.verbose:
190
                    self.note("get_inventories cache miss for %s", revision_id)
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
191
                # Not cached so reconstruct from the revision store
192
                try:
193
                    inv = self.get_inventory(revision_id)
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
194
                    present.append(revision_id)
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
195
                except:
196
                    inv = self.gen_initial_inventory()
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
197
                self.cache_mgr.inventories[revision_id] = inv
198
            inventories.append(inv)
199
        return present, inventories
200
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
201
    def modify_handler(self, filecmd):
202
        if filecmd.dataref is not None:
203
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
204
        else:
205
            data = filecmd.data
206
        self.debug("modifying %s", filecmd.path)
207
        self._modify_inventory(filecmd.path, filecmd.kind,
208
            filecmd.is_executable, data)
209
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
210
    def delete_handler(self, filecmd):
211
        self._delete_recursive(filecmd.path)
212
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
213
    def _delete_recursive(self, path):
214
        self.debug("deleting %s", path)
215
        fileid = self.bzr_file_id(path)
216
        dirname, basename = osutils.split(path)
217
        if (fileid in self.inventory and
218
            isinstance(self.inventory[fileid], inventory.InventoryDirectory)):
219
            for child_path in self.inventory[fileid].children.keys():
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
220
                self._delete_recursive(osutils.pathjoin(path, child_path))
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
221
        try:
222
            if self.inventory.id2path(fileid) == path:
223
                del self.inventory[fileid]
224
            else:
225
                # already added by some other name?
226
                if dirname in self.cache_mgr.file_ids:
227
                    parent_id = self.cache_mgr.file_ids[dirname]
228
                    del self.inventory[parent_id].children[basename]
229
        except KeyError:
230
            self._warn_unless_in_merges(fileid, path)
231
        except errors.NoSuchId:
232
            self._warn_unless_in_merges(fileid, path)
233
        except AttributeError, ex:
234
            if ex.args[0] == 'children':
235
                # A directory has changed into a file and then one
236
                # of it's children is being deleted!
237
                self._warn_unless_in_merges(fileid, path)
238
            else:
239
                raise
240
        try:
241
            self.cache_mgr.delete_path(path)
242
        except KeyError:
243
            pass
244
245
    def copy_handler(self, filecmd):
246
        src_path = filecmd.src_path
247
        dest_path = filecmd.dest_path
248
        self.debug("copying %s to %s", src_path, dest_path)
249
        if not self.parents:
250
            self.warning("ignoring copy of %s to %s - no parent revisions",
251
                src_path, dest_path)
252
            return
253
        file_id = self.inventory.path2id(src_path)
254
        if file_id is None:
255
            self.warning("ignoring copy of %s to %s - source does not exist",
256
                src_path, dest_path)
257
            return
258
        ie = self.inventory[file_id]
259
        kind = ie.kind
260
        if kind == 'file':
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
261
            content = self.rev_store.get_file_text(self.parents[0], file_id)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
262
            self._modify_inventory(dest_path, kind, ie.executable, content)
263
        elif kind == 'symlink':
264
            self._modify_inventory(dest_path, kind, False, ie.symlink_target)
265
        else:
266
            self.warning("ignoring copy of %s %s - feature not yet supported",
267
                kind, path)
268
269
    def rename_handler(self, filecmd):
270
        old_path = filecmd.old_path
271
        new_path = filecmd.new_path
272
        self.debug("renaming %s to %s", old_path, new_path)
273
        file_id = self.bzr_file_id(old_path)
274
        basename, new_parent_ie = self._ensure_directory(new_path)
275
        new_parent_id = new_parent_ie.file_id
276
        existing_id = self.inventory.path2id(new_path)
277
        if existing_id is not None:
278
            self.inventory.remove_recursive_id(existing_id)
279
        ie = self.inventory[file_id]
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
280
        lines = self.rev_store._get_lines(file_id, ie.revision)
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
281
        self.lines_for_commit[file_id] = lines
282
        self.inventory.rename(file_id, new_parent_id, basename)
283
        self.cache_mgr.rename_path(old_path, new_path)
284
        self.inventory[file_id].revision = self.revision_id
285
286
    def deleteall_handler(self, filecmd):
287
        self.debug("deleting all files (and also all directories)")
288
        # Would be nice to have an inventory.clear() method here
289
        root_items = [ie for (name, ie) in
290
            self.inventory.root.children.iteritems()]
291
        for root_item in root_items:
292
            self.inventory.remove_recursive_id(root_item.file_id)
293
294
    def _modify_inventory(self, path, kind, is_executable, data):
295
        """Add to or change an item in the inventory."""
296
        # Create the new InventoryEntry
297
        basename, parent_ie = self._ensure_directory(path)
298
        file_id = self.bzr_file_id(path)
299
        ie = inventory.make_entry(kind, basename, parent_ie.file_id, file_id)
300
        ie.revision = self.revision_id
301
        if isinstance(ie, inventory.InventoryFile):
302
            ie.executable = is_executable
303
            lines = osutils.split_lines(data)
304
            ie.text_sha1 = osutils.sha_strings(lines)
305
            ie.text_size = sum(map(len, lines))
306
            self.lines_for_commit[file_id] = lines
307
        elif isinstance(ie, inventory.InventoryLink):
308
            ie.symlink_target = data.encode('utf8')
309
            # There are no lines stored for a symlink so
310
            # make sure the cache used by get_lines knows that
311
            self.lines_for_commit[file_id] = []
312
        else:
313
            raise errors.BzrError("Cannot import items of kind '%s' yet" %
314
                (kind,))
315
316
        # Record this new inventory entry
317
        if file_id in self.inventory:
318
            # HACK: no API for this (del+add does more than it needs to)
319
            self.inventory._byid[file_id] = ie
320
            parent_ie.children[basename] = ie
321
        else:
322
            self.inventory.add(ie)
323
324
    def _ensure_directory(self, path):
325
        """Ensure that the containing directory exists for 'path'"""
326
        dirname, basename = osutils.split(path)
327
        if dirname == '':
328
            # the root node doesn't get updated
329
            return basename, self.inventory.root
330
        try:
331
            ie = self.directory_entries[dirname]
332
        except KeyError:
333
            # We will create this entry, since it doesn't exist
334
            pass
335
        else:
336
            return basename, ie
337
338
        # No directory existed, we will just create one, first, make sure
339
        # the parent exists
340
        dir_basename, parent_ie = self._ensure_directory(dirname)
341
        dir_file_id = self.bzr_file_id(dirname)
342
        ie = inventory.entry_factory['directory'](dir_file_id,
343
                                                  dir_basename,
344
                                                  parent_ie.file_id)
345
        ie.revision = self.revision_id
346
        self.directory_entries[dirname] = ie
347
        # There are no lines stored for a directory so
348
        # make sure the cache used by get_lines knows that
349
        self.lines_for_commit[dir_file_id] = []
350
        #print "adding dir for %s" % path
351
        self.inventory.add(ie)
352
        return basename, ie