/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
    )
0.64.229 by Ian Clatworthy
Handle git submodules in the stream by warning about + ignoring them
28
from bzrlib.plugins.fastimport import commands, helpers, processor
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
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.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
56
        # This tracks path->file-id for things we're creating this commit.
57
        # If the same path is created multiple times, we need to warn the
58
        # user and add it just once.
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
59
        # If a path is added then renamed or copied, we need to handle that.
0.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
60
        self._new_file_ids = {}
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
61
        # This tracks path->file-id for things we're modifying this commit.
62
        # If a path is modified then renamed or copied, we need the make
63
        # sure we grab the new content.
64
        self._modified_file_ids = {}
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
65
        # This tracks the paths for things we're deleting this commit.
66
        # If the same path is added or the destination of a rename say,
67
        # then a fresh file-id is required.
68
        self._paths_deleted_this_commit = set()
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
69
70
    def pre_process_files(self):
71
        """Prepare for committing."""
72
        self.revision_id = self.gen_revision_id()
73
        # cache of texts for this commit, indexed by file-id
74
        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
75
        #if self.rev_store.expects_rich_root():
76
        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
77
78
        # Track the heads and get the real parent list
79
        parents = self.cache_mgr.track_heads(self.command)
80
81
        # Convert the parent commit-ids to bzr revision-ids
82
        if parents:
83
            self.parents = [self.cache_mgr.revision_ids[p]
84
                for p in parents]
85
        else:
86
            self.parents = []
87
        self.debug("%s id: %s, parents: %s", self.command.id,
88
            self.revision_id, str(self.parents))
89
0.85.2 by Ian Clatworthy
improve per-file graph generation
90
        # Tell the RevisionStore we're starting a new commit
91
        self.revision = self.build_revision()
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
92
        self.parent_invs = [self.get_inventory(p) for p in self.parents]
0.85.2 by Ian Clatworthy
improve per-file graph generation
93
        self.rev_store.start_new_revision(self.revision, self.parents,
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
94
            self.parent_invs)
0.85.2 by Ian Clatworthy
improve per-file graph generation
95
96
        # cache of per-file parents for this commit, indexed by file-id
97
        self.per_file_parents_for_commit = {}
98
        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
99
            self.per_file_parents_for_commit[inventory.ROOT_ID] = ()
0.85.2 by Ian Clatworthy
improve per-file graph generation
100
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
101
        # Keep the basis inventory. This needs to be treated as read-only.
102
        if len(self.parents) == 0:
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
103
            self.basis_inventory = self._init_inventory()
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
104
        else:
105
            self.basis_inventory = self.get_inventory(self.parents[0])
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
106
        if hasattr(self.basis_inventory, "root_id"):
107
            self.inventory_root_id = self.basis_inventory.root_id
108
        else:
109
            self.inventory_root_id = self.basis_inventory.root.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
110
111
        # directory-path -> inventory-entry for current inventory
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
112
        self.directory_entries = {}
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
113
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
114
    def _init_inventory(self):
115
        return self.rev_store.init_inventory(self.revision_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
116
117
    def get_inventory(self, revision_id):
118
        """Get the inventory for a revision id."""
119
        try:
120
            inv = self.cache_mgr.inventories[revision_id]
121
        except KeyError:
122
            if self.verbose:
0.64.148 by Ian Clatworthy
handle delete of unknown file in chk formats & reduce noise
123
                self.mutter("get_inventory cache miss for %s", revision_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
124
            # Not cached so reconstruct from the RevisionStore
125
            inv = self.rev_store.get_inventory(revision_id)
126
            self.cache_mgr.inventories[revision_id] = inv
127
        return inv
128
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
129
    def _get_lines(self, file_id):
130
        """Get the lines for a file-id."""
131
        return self.lines_for_commit[file_id]
132
0.85.2 by Ian Clatworthy
improve per-file graph generation
133
    def _get_per_file_parents(self, file_id):
134
        """Get the lines for a file-id."""
135
        return self.per_file_parents_for_commit[file_id]
136
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
137
    def _get_inventories(self, revision_ids):
138
        """Get the inventories for revision-ids.
139
        
140
        This is a callback used by the RepositoryStore to
141
        speed up inventory reconstruction.
142
        """
143
        present = []
144
        inventories = []
145
        # If an inventory is in the cache, we assume it was
146
        # successfully loaded into the revision store
147
        for revision_id in revision_ids:
148
            try:
149
                inv = self.cache_mgr.inventories[revision_id]
150
                present.append(revision_id)
151
            except KeyError:
152
                if self.verbose:
153
                    self.note("get_inventories cache miss for %s", revision_id)
154
                # Not cached so reconstruct from the revision store
155
                try:
156
                    inv = self.get_inventory(revision_id)
157
                    present.append(revision_id)
158
                except:
0.84.4 by Ian Clatworthy
improved-but-not-yet-working CHKInventory support
159
                    inv = self._init_inventory()
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
160
                self.cache_mgr.inventories[revision_id] = inv
161
            inventories.append(inv)
162
        return present, inventories
163
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
164
    def bzr_file_id_and_new(self, path):
165
        """Get a Bazaar file identifier and new flag for a path.
166
        
167
        :return: file_id, is_new where
168
          is_new = True if the file_id is newly created
169
        """
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
170
        if path not in self._paths_deleted_this_commit:
0.99.19 by Ian Clatworthy
Handle rename then modification of the new path
171
            # Try file-ids renamed in this commit
172
            id = self._modified_file_ids.get(path)
173
            if id is not None:
174
                return id, False
175
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
176
            # Try the basis inventory
177
            id = self.basis_inventory.path2id(path)
178
            if id is not None:
179
                return id, False
180
            
181
            # Try the other inventories
182
            if len(self.parents) > 1:
183
                for inv in self.parent_invs[1:]:
184
                    id = self.basis_inventory.path2id(path)
185
                    if id is not None:
186
                        return id, False
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
187
188
        # Doesn't exist yet so create it
189
        id = generate_ids.gen_file_id(path)
190
        self.debug("Generated new file id %s for '%s' in revision-id '%s'",
191
            id, path, self.revision_id)
0.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
192
        self._new_file_ids[path] = id
0.99.1 by Ian Clatworthy
lookup file-ids in inventories instead of a cache
193
        return id, True
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
194
195
    def bzr_file_id(self, path):
196
        """Get a Bazaar file identifier for a path."""
197
        return self.bzr_file_id_and_new(path)[0]
198
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
199
    def _format_name_email(self, name, email):
200
        """Format name & email as a string."""
201
        if email:
202
            return "%s <%s>" % (name, email)
203
        else:
204
            return name
205
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
206
    def gen_revision_id(self):
207
        """Generate a revision id.
208
209
        Subclasses may override this to produce deterministic ids say.
210
        """
211
        committer = self.command.committer
212
        # Perhaps 'who' being the person running the import is ok? If so,
213
        # 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
214
        who = self._format_name_email(committer[0], committer[1])
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
215
        timestamp = committer[2]
216
        return generate_ids.gen_revision_id(who, timestamp)
217
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
218
    def build_revision(self):
219
        rev_props = {}
220
        committer = self.command.committer
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
221
        who = self._format_name_email(committer[0], committer[1])
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
222
        author = self.command.author
223
        if author is not None:
0.64.177 by Ian Clatworthy
fix round-tripping of committer & author when name is an email
224
            author_id = self._format_name_email(author[0], author[1])
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
225
            if author_id != who:
226
                rev_props['author'] = author_id
0.64.192 by Ian Clatworthy
delegate commit message escaping to the serializer if it's a modern one
227
        message = self.command.message
228
        if not _serializer_handles_escaping:
229
            # We need to assume the bad ol' days
230
            message = helpers.escape_commit_message(message)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
231
        return revision.Revision(
232
           timestamp=committer[2],
233
           timezone=committer[3],
234
           committer=who,
0.64.192 by Ian Clatworthy
delegate commit message escaping to the serializer if it's a modern one
235
           message=message,
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
236
           revision_id=self.revision_id,
237
           properties=rev_props,
238
           parent_ids=self.parents)
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
239
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
240
    def _modify_item(self, path, kind, is_executable, data, inv):
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
241
        """Add to or change an item in the inventory."""
0.99.5 by Ian Clatworthy
handle adding the same file twice in the one commit
242
        # If we've already added this, warn the user that we're ignoring it.
243
        # In the future, it might be nice to double check that the new data
244
        # is the same as the old but, frankly, exporters should be fixed
245
        # not to produce bad data streams in the first place ...
246
        existing = self._new_file_ids.get(path)
247
        if existing:
248
            self.warning("%s already added in this commit - ignoring" % (path,))
249
            return
250
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
251
        # Create the new InventoryEntry
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
252
        basename, parent_id = self._ensure_directory(path, inv)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
253
        file_id = self.bzr_file_id(path)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
254
        ie = inventory.make_entry(kind, basename, parent_id, file_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
255
        ie.revision = self.revision_id
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
256
        if kind == 'file':
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
257
            ie.executable = is_executable
258
            lines = osutils.split_lines(data)
259
            ie.text_sha1 = osutils.sha_strings(lines)
260
            ie.text_size = sum(map(len, lines))
261
            self.lines_for_commit[file_id] = lines
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
262
        elif kind == 'symlink':
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
263
            ie.symlink_target = data.encode('utf8')
264
            # There are no lines stored for a symlink so
265
            # make sure the cache used by get_lines knows that
266
            self.lines_for_commit[file_id] = []
267
        else:
0.64.229 by Ian Clatworthy
Handle git submodules in the stream by warning about + ignoring them
268
            self.warning("Cannot import items of kind '%s' yet - ignoring '%s'"
269
                % (kind, path))
270
            return
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
271
        # Record it
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
272
        if file_id in inv:
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
273
            old_ie = inv[file_id]
274
            if old_ie.kind == 'directory':
275
                self.record_delete(path, old_ie)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
276
            self.record_changed(path, ie, parent_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
277
        else:
0.64.165 by Ian Clatworthy
handle adding a file to a dir deleted in the same commit
278
            try:
279
                self.record_new(path, ie)
280
            except:
0.64.167 by Ian Clatworthy
incremental packing for chk formats
281
                print "failed to add path '%s' with entry '%s' in command %s" \
282
                    % (path, ie, self.command.id)
283
                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
284
                raise
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
285
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
286
    def _ensure_directory(self, path, inv):
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
287
        """Ensure that the containing directory exists for 'path'"""
288
        dirname, basename = osutils.split(path)
289
        if dirname == '':
290
            # the root node doesn't get updated
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
291
            return basename, self.inventory_root_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
292
        try:
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
293
            ie = self._get_directory_entry(inv, dirname)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
294
        except KeyError:
295
            # We will create this entry, since it doesn't exist
296
            pass
297
        else:
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
298
            return basename, ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
299
300
        # No directory existed, we will just create one, first, make sure
301
        # the parent exists
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
302
        dir_basename, parent_id = self._ensure_directory(dirname, inv)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
303
        dir_file_id = self.bzr_file_id(dirname)
304
        ie = inventory.entry_factory['directory'](dir_file_id,
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
305
            dir_basename, parent_id)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
306
        ie.revision = self.revision_id
307
        self.directory_entries[dirname] = ie
308
        # There are no lines stored for a directory so
309
        # make sure the cache used by get_lines knows that
310
        self.lines_for_commit[dir_file_id] = []
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
311
312
        # It's possible that a file or symlink with that file-id
313
        # already exists. If it does, we need to delete it.
314
        if dir_file_id in inv:
315
            self.record_delete(dirname, ie)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
316
        self.record_new(dirname, ie)
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
317
        return basename, ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
318
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
319
    def _get_directory_entry(self, inv, dirname):
320
        """Get the inventory entry for a directory.
321
        
322
        Raises KeyError if dirname is not a directory in inv.
323
        """
324
        result = self.directory_entries.get(dirname)
325
        if result is None:
0.99.21 by Ian Clatworthy
Handle deleting a directory then adding a file within it in the same commit
326
            if dirname in self._paths_deleted_this_commit:
327
                raise KeyError
0.64.146 by Ian Clatworthy
fix first file is in a subdirectory bug for chk formats
328
            try:
329
                file_id = inv.path2id(dirname)
330
            except errors.NoSuchId:
331
                # In a CHKInventory, this is raised if there's no root yet
332
                raise KeyError
0.84.12 by Ian Clatworthy
lookup directories on demand in CHKInventories, not all upfront
333
            if file_id is None:
334
                raise KeyError
335
            result = inv[file_id]
336
            # dirname must be a directory for us to return it
337
            if result.kind == 'directory':
338
                self.directory_entries[dirname] = result
339
            else:
340
                raise KeyError
341
        return result
342
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
343
    def _delete_item(self, path, inv):
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
344
        newly_added = self._new_file_ids.get(path)
345
        if newly_added:
346
            # We've only just added this path earlier in this commit.
347
            file_id = newly_added
348
            # note: delta entries look like (old, new, file-id, ie)
349
            ie = self._delta_entries_by_fileid[file_id][3]
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
350
        else:
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
351
            file_id = inv.path2id(path)
352
            if file_id is None:
353
                self.mutter("ignoring delete of %s as not in inventory", path)
354
                return
355
            try:
356
                ie = inv[file_id]
357
            except errors.NoSuchId:
358
                self.mutter("ignoring delete of %s as not in inventory", path)
359
                return
360
        self.record_delete(path, ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
361
362
    def _copy_item(self, src_path, dest_path, inv):
0.99.18 by Ian Clatworthy
Handle copy of a file/symlink already modified in this commit
363
        newly_changed = self._new_file_ids.get(src_path) or \
364
            self._modified_file_ids.get(src_path)
365
        if newly_changed:
366
            # We've only just added/changed this path earlier in this commit.
367
            file_id = newly_changed
0.99.8 by Ian Clatworthy
handle copy of a newly added file
368
            # note: delta entries look like (old, new, file-id, ie)
369
            ie = self._delta_entries_by_fileid[file_id][3]
370
        else:
371
            file_id = inv.path2id(src_path)
372
            if file_id is None:
373
                self.warning("ignoring copy of %s to %s - source does not exist",
374
                    src_path, dest_path)
375
                return
376
            ie = inv[file_id]
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
377
        kind = ie.kind
378
        if kind == 'file':
0.99.18 by Ian Clatworthy
Handle copy of a file/symlink already modified in this commit
379
            if newly_changed:
0.99.8 by Ian Clatworthy
handle copy of a newly added file
380
                content = ''.join(self.lines_for_commit[file_id])
381
            else:
382
                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
383
            self._modify_item(dest_path, kind, ie.executable, content, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
384
        elif kind == 'symlink':
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
385
            self._modify_item(dest_path, kind, False, ie.symlink_target, inv)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
386
        else:
387
            self.warning("ignoring copy of %s %s - feature not yet supported",
388
                kind, path)
389
390
    def _rename_item(self, old_path, new_path, inv):
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
391
        existing = self._new_file_ids.get(old_path) or \
392
            self._modified_file_ids.get(old_path)
0.99.6 by Ian Clatworthy
Handle rename of a just added file
393
        if existing:
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
394
            # We've only just added/modified this path earlier in this commit.
395
            # Change the add/modify of old_path to an add of new_path
396
            self._rename_pending_change(old_path, new_path, existing)
0.99.6 by Ian Clatworthy
Handle rename of a just added file
397
            return
398
0.81.8 by Ian Clatworthy
refactor rename_item
399
        file_id = inv.path2id(old_path)
0.64.167 by Ian Clatworthy
incremental packing for chk formats
400
        if file_id is None:
401
            self.warning(
402
                "ignoring rename of %s to %s - old path does not exist" %
403
                (old_path, new_path))
404
            return
0.81.8 by Ian Clatworthy
refactor rename_item
405
        ie = inv[file_id]
406
        rev_id = ie.revision
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
407
        new_file_id = inv.path2id(new_path)
408
        if new_file_id is not None:
0.81.9 by Ian Clatworthy
refactor delete_item
409
            self.record_delete(new_path, inv[new_file_id])
0.81.8 by Ian Clatworthy
refactor rename_item
410
        self.record_rename(old_path, new_path, file_id, ie)
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
411
0.81.8 by Ian Clatworthy
refactor rename_item
412
        # 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
413
        # that means the loader then needs to know what the "new" text is.
414
        # We therefore must go back to the revision store to get it.
0.81.8 by Ian Clatworthy
refactor rename_item
415
        lines = self.rev_store.get_file_lines(rev_id, file_id)
416
        self.lines_for_commit[file_id] = lines
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
417
418
    def _delete_all_items(self, inv):
419
        for name, root_item in inv.root.children.iteritems():
420
            inv.remove_recursive_id(root_item.file_id)
421
0.64.145 by Ian Clatworthy
handle delete of missing files for chk formats
422
    def _warn_unless_in_merges(self, fileid, path):
423
        if len(self.parents) <= 1:
424
            return
425
        for parent in self.parents[1:]:
426
            if fileid in self.get_inventory(parent):
427
                return
428
        self.warning("ignoring delete of %s as not in parent inventories", path)
429
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
430
431
class InventoryCommitHandler(GenericCommitHandler):
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
432
    """A CommitHandler that builds and saves Inventory objects."""
0.81.2 by Ian Clatworthy
refactor InventoryCommitHandler general stuff into parent class
433
434
    def pre_process_files(self):
435
        super(InventoryCommitHandler, self).pre_process_files()
436
0.64.159 by Ian Clatworthy
make the file-id cache optional and branch-ref aware
437
        # Seed the inventory from the previous one. Note that
438
        # the parent class version of pre_process_files() has
439
        # already set the right basis_inventory for this branch
440
        # but we need to copy it in order to mutate it safely
441
        # 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
442
        if len(self.parents) == 0:
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
443
            self.inventory = self.basis_inventory
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
444
        else:
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
445
            self.inventory = copy_inventory(self.basis_inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
446
        self.inventory_root = self.inventory.root
447
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
448
        # directory-path -> inventory-entry for current inventory
449
        self.directory_entries = dict(self.inventory.directories())
450
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
451
        # Initialise the inventory revision info as required
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
452
        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
453
            self.inventory.revision_id = self.revision_id
454
        else:
0.81.4 by Ian Clatworthy
generalise RevisionLoader to RevisionStore as a repo abstraction
455
            # In this revision store, root entries have no knit or weave.
456
            # When serializing out to disk and back in, root.revision is
457
            # always the new revision_id.
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
458
            self.inventory.root.revision = self.revision_id
459
460
    def post_process_files(self):
461
        """Save the revision."""
462
        self.cache_mgr.inventories[self.revision_id] = self.inventory
0.85.2 by Ian Clatworthy
improve per-file graph generation
463
        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
464
            lambda file_id: self._get_lines(file_id),
0.85.2 by Ian Clatworthy
improve per-file graph generation
465
            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
466
            lambda revision_ids: self._get_inventories(revision_ids))
467
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
468
    def record_new(self, path, ie):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
469
        try:
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
470
            # If this is a merge, the file was most likely added already.
471
            # The per-file parent(s) must therefore be calculated and
472
            # we can't assume there are none.
473
            per_file_parents, ie.revision = \
474
                self.rev_store.get_parents_and_revision_for_entry(ie)
475
            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
476
            self.inventory.add(ie)
477
        except errors.DuplicateFileId:
478
            # Directory already exists as a file or symlink
479
            del self.inventory[ie.file_id]
480
            # Try again
481
            self.inventory.add(ie)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
482
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
483
    def record_changed(self, path, ie, parent_id):
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
484
        # HACK: no API for this (del+add does more than it needs to)
0.85.2 by Ian Clatworthy
improve per-file graph generation
485
        per_file_parents, ie.revision = \
486
            self.rev_store.get_parents_and_revision_for_entry(ie)
487
        self.per_file_parents_for_commit[ie.file_id] = per_file_parents
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
488
        self.inventory._byid[ie.file_id] = ie
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
489
        parent_ie = self.inventory._byid[parent_id]
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
490
        parent_ie.children[ie.name] = ie
491
0.81.9 by Ian Clatworthy
refactor delete_item
492
    def record_delete(self, path, ie):
493
        self.inventory.remove_recursive_id(ie.file_id)
0.81.8 by Ian Clatworthy
refactor rename_item
494
495
    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
496
        # For a rename, the revision-id is always the new one so
497
        # no need to change/set it here
498
        ie.revision = self.revision_id
499
        per_file_parents, _ = \
500
            self.rev_store.get_parents_and_revision_for_entry(ie)
501
        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
502
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
503
            self.inventory)
0.81.8 by Ian Clatworthy
refactor rename_item
504
        self.inventory.rename(file_id, new_parent_id, new_basename)
505
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
506
    def modify_handler(self, filecmd):
507
        if filecmd.dataref is not None:
508
            data = self.cache_mgr.fetch_blob(filecmd.dataref)
509
        else:
510
            data = filecmd.data
511
        self.debug("modifying %s", filecmd.path)
512
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
513
            filecmd.is_executable, data, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
514
515
    def delete_handler(self, filecmd):
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
516
        self.debug("deleting %s", filecmd.path)
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
517
        self._delete_item(filecmd.path, self.inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
518
0.81.1 by Ian Clatworthy
move GenericCommitHandler into its own module in prep for a delta-based one
519
    def copy_handler(self, filecmd):
520
        src_path = filecmd.src_path
521
        dest_path = filecmd.dest_path
522
        self.debug("copying %s to %s", src_path, dest_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
523
        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
524
525
    def rename_handler(self, filecmd):
526
        old_path = filecmd.old_path
527
        new_path = filecmd.new_path
528
        self.debug("renaming %s to %s", old_path, new_path)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
529
        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
530
531
    def deleteall_handler(self, filecmd):
532
        self.debug("deleting all files (and also all directories)")
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
533
        self._delete_all_items(self.inventory)
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
534
535
0.64.171 by Ian Clatworthy
use inv deltas by default for all formats now: --classic to get old algorithm for packs
536
class InventoryDeltaCommitHandler(GenericCommitHandler):
537
    """A CommitHandler that builds Inventories by applying a delta."""
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
538
539
    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
540
        super(InventoryDeltaCommitHandler, self).pre_process_files()
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
541
        self._dirs_that_might_become_empty = set()
542
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
543
        # A given file-id can only appear once so we accumulate
544
        # the entries in a dict then build the actual delta at the end
545
        self._delta_entries_by_fileid = {}
0.84.7 by Ian Clatworthy
CHKInventory support for non rich-root repos working, for simple imports at least
546
        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
547
            if self.parents:
548
                old_path = ''
549
            else:
550
                old_path = None
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
551
            # 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
552
            # and for non rich-root inventories
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
553
            root_id = inventory.ROOT_ID
554
            root_ie = inventory.InventoryDirectory(root_id, u'', None)
555
            root_ie.revision = self.revision_id
0.84.10 by Ian Clatworthy
fix TREE_ROOT delta entry after 1st revision & tweak _delete_item usage
556
            self._add_entry((old_path, '', root_id, root_ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
557
558
    def post_process_files(self):
559
        """Save the revision."""
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
560
        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
561
        inv = self.rev_store.load_using_delta(self.revision,
562
            self.basis_inventory, delta, None,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
563
            lambda file_id: self._get_lines(file_id),
0.85.2 by Ian Clatworthy
improve per-file graph generation
564
            lambda file_id: self._get_per_file_parents(file_id),
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
565
            lambda revision_ids: self._get_inventories(revision_ids))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
566
        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
567
        #print "committed %s" % self.revision_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
568
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
569
    def _get_final_delta(self):
570
        """Generate the final delta.
571
572
        Smart post-processing of changes, e.g. pruning of directories
573
        that would become empty, goes here.
574
        """
575
        delta = list(self._delta_entries_by_fileid.values())
576
        if self.prune_empty_dirs and self._dirs_that_might_become_empty:
577
            candidates = osutils.minimum_path_selection(
578
                self._dirs_that_might_become_empty)
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
579
            never_born = set()
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
580
            for path, file_id in self._empty_after_delta(delta, candidates):
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
581
                newly_added = self._new_file_ids.get(path)
582
                if newly_added:
583
                    never_born.add(newly_added)
584
                else:
585
                    delta.append((path, None, file_id, None))
586
            # Clean up entries that got deleted before they were ever added
587
            if never_born:
588
                delta = [de for de in delta if de[2] not in never_born]
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
589
        return delta
590
591
    def _empty_after_delta(self, delta, candidates):
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
592
        #self.mutter("delta so far is:\n%s" % "\n".join([str(de) for de in delta]))
593
        #self.mutter("candidates for deletion are:\n%s" % "\n".join([c for c in candidates]))
594
        new_inv = self._get_proposed_inventory(delta)
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
595
        result = []
596
        for dir in candidates:
597
            file_id = new_inv.path2id(dir)
0.64.219 by Ian Clatworthy
More robust implicit delete logic when file-id not found
598
            if file_id is None:
599
                continue
0.96.2 by Ian Clatworthy
test and fix for implicit directory delete recursing up
600
            ie = new_inv[file_id]
601
            if len(ie.children) == 0:
602
                result.append((dir, file_id))
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
603
                if self.verbose:
604
                    self.note("pruning empty directory %s" % (dir,))
0.96.2 by Ian Clatworthy
test and fix for implicit directory delete recursing up
605
                # Check parents in case deleting this dir makes *them* empty
606
                while True:
607
                    file_id = ie.parent_id
608
                    if file_id == inventory.ROOT_ID:
609
                        # We've reach the root
610
                        break
611
                    try:
612
                        ie = new_inv[file_id]
613
                    except errors.NoSuchId:
614
                        break
615
                    if len(ie.children) > 1:
616
                        break
617
                    dir = new_inv.id2path(file_id)
618
                    result.append((dir, file_id))
619
                    if self.verbose:
620
                        self.note("pruning empty directory parent %s" % (dir,))
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
621
        return result
622
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
623
    def _get_proposed_inventory(self, delta):
624
        if len(self.parents):
625
            new_inv = self.basis_inventory._get_mutable_inventory()
626
        else:
627
            new_inv = inventory.Inventory(revision_id=self.revision_id)
628
            # This is set in the delta so remove it to prevent a duplicate
629
            del new_inv[inventory.ROOT_ID]
0.99.9 by Ian Clatworthy
better diagnostics on inconsistent delta
630
        try:
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
631
            new_inv.apply_delta(delta)
0.99.9 by Ian Clatworthy
better diagnostics on inconsistent delta
632
        except errors.InconsistentDelta:
633
            self.mutter("INCONSISTENT DELTA IS:\n%s" % "\n".join([str(de) for de in delta]))
634
            raise
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
635
        return new_inv
636
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
637
    def _add_entry(self, entry):
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
638
        # 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
639
        # For example, a rename followed by a modification looks like:
640
        #
641
        # (x, y, f, e) & (y, y, f, g) => (x, y, f, g)
642
        #
643
        # Likewise, a modification followed by a rename looks like:
644
        #
645
        # (x, x, f, e) & (x, y, f, g) => (x, y, f, g)
646
        #
647
        # Here's a rename followed by a delete and a modification followed by
648
        # a delete:
649
        #
650
        # (x, y, f, e) & (y, None, f, None) => (x, None, f, None)
651
        # (x, x, f, e) & (x, None, f, None) => (x, None, f, None)
652
        #
653
        # In summary, we use the original old-path, new new-path and new ie
654
        # when combining entries.
0.85.2 by Ian Clatworthy
improve per-file graph generation
655
        old_path = entry[0]
656
        new_path = entry[1]
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
657
        file_id = entry[2]
0.85.2 by Ian Clatworthy
improve per-file graph generation
658
        ie = entry[3]
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
659
        existing = self._delta_entries_by_fileid.get(file_id, None)
660
        if existing is not None:
0.85.2 by Ian Clatworthy
improve per-file graph generation
661
            old_path = existing[0]
662
            entry = (old_path, new_path, file_id, ie)
0.99.6 by Ian Clatworthy
Handle rename of a just added file
663
        if new_path is None and old_path is None:
664
            # This is a delete cancelling a previous add
665
            del self._delta_entries_by_fileid[file_id]
0.99.7 by Ian Clatworthy
handle a delete of a newly added file
666
            parent_dir = osutils.dirname(existing[1])
667
            self.mutter("cancelling add of %s with parent %s" % (existing[1], parent_dir))
668
            if parent_dir:
669
                self._dirs_that_might_become_empty.add(parent_dir)
0.99.6 by Ian Clatworthy
Handle rename of a just added file
670
            return
671
        else:
672
            self._delta_entries_by_fileid[file_id] = entry
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
673
0.99.6 by Ian Clatworthy
Handle rename of a just added file
674
        # Collect parent directories that might become empty
0.64.195 by Ian Clatworthy
prune directories that become empty after a delete or rename
675
        if new_path is None:
676
            # delete
677
            parent_dir = osutils.dirname(old_path)
678
            # note: no need to check the root
679
            if parent_dir:
680
                self._dirs_that_might_become_empty.add(parent_dir)
681
        elif old_path is not None and old_path != new_path:
682
            # rename
683
            old_parent_dir = osutils.dirname(old_path)
684
            new_parent_dir = osutils.dirname(new_path)
685
            if old_parent_dir and old_parent_dir != new_parent_dir:
686
                self._dirs_that_might_become_empty.add(old_parent_dir)
687
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
688
        # Calculate the per-file parents, if not already done
689
        if file_id in self.per_file_parents_for_commit:
690
            return
0.85.2 by Ian Clatworthy
improve per-file graph generation
691
        if old_path is None:
692
            # add
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
693
            # If this is a merge, the file was most likely added already.
694
            # The per-file parent(s) must therefore be calculated and
695
            # we can't assume there are none.
696
            per_file_parents, ie.revision = \
697
                self.rev_store.get_parents_and_revision_for_entry(ie)
698
            self.per_file_parents_for_commit[file_id] = per_file_parents
0.85.2 by Ian Clatworthy
improve per-file graph generation
699
        elif new_path is None:
700
            # delete
701
            pass
702
        elif old_path != new_path:
703
            # rename
0.64.161 by Ian Clatworthy
fix per-graph parent handling for adds and renames
704
            per_file_parents, _ = \
705
                self.rev_store.get_parents_and_revision_for_entry(ie)
706
            self.per_file_parents_for_commit[file_id] = per_file_parents
0.85.2 by Ian Clatworthy
improve per-file graph generation
707
        else:
708
            # modify
709
            per_file_parents, ie.revision = \
710
                self.rev_store.get_parents_and_revision_for_entry(ie)
711
            self.per_file_parents_for_commit[file_id] = per_file_parents
712
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
713
    def record_new(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
714
        self._add_entry((None, path, ie.file_id, ie))
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
715
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
716
    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
717
        self._add_entry((path, path, ie.file_id, ie))
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
718
        self._modified_file_ids[path] = ie.file_id
0.81.5 by Ian Clatworthy
basic DeltaCommitHandler generating deltas
719
0.81.9 by Ian Clatworthy
refactor delete_item
720
    def record_delete(self, path, ie):
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
721
        self._add_entry((path, None, ie.file_id, None))
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
722
        self._paths_deleted_this_commit.add(path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
723
        if ie.kind == 'directory':
0.99.21 by Ian Clatworthy
Handle deleting a directory then adding a file within it in the same commit
724
            try:
725
                del self.directory_entries[path]
726
            except KeyError:
727
                pass
0.64.187 by Ian Clatworthy
fix inv-delta generation when deleting directories
728
            for child_relpath, entry in \
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
729
                self.basis_inventory.iter_entries_by_dir(from_dir=ie):
0.64.187 by Ian Clatworthy
fix inv-delta generation when deleting directories
730
                child_path = osutils.pathjoin(path, child_relpath)
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
731
                self._add_entry((child_path, None, entry.file_id, None))
0.99.13 by Ian Clatworthy
Handle delete then add of a file/symlink in the one commit
732
                self._paths_deleted_this_commit.add(child_path)
0.99.21 by Ian Clatworthy
Handle deleting a directory then adding a file within it in the same commit
733
                if entry.kind == 'directory':
734
                    try:
735
                        del self.directory_entries[child_path]
736
                    except KeyError:
737
                        pass
0.81.8 by Ian Clatworthy
refactor rename_item
738
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
739
    def record_rename(self, old_path, new_path, file_id, old_ie):
740
        new_ie = old_ie.copy()
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
741
        new_basename, new_parent_id = self._ensure_directory(new_path,
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
742
            self.basis_inventory)
743
        new_ie.name = new_basename
0.84.5 by Ian Clatworthy
_ensure_directory to return parent_id, not parent_ie
744
        new_ie.parent_id = new_parent_id
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
745
        new_ie.revision = self.revision_id
0.84.9 by Ian Clatworthy
get non-chk formats working again & combine delta entries when required
746
        self._add_entry((old_path, new_path, file_id, new_ie))
0.99.19 by Ian Clatworthy
Handle rename then modification of the new path
747
        self._modified_file_ids[new_path] = file_id
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
748
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
749
    def _rename_pending_change(self, old_path, new_path, file_id):
750
        """Instead of adding/modifying old-path, add new-path instead."""
0.99.6 by Ian Clatworthy
Handle rename of a just added file
751
        # note: delta entries look like (old, new, file-id, ie)
752
        old_ie = self._delta_entries_by_fileid[file_id][3]
753
754
        # Delete the old path. Note that this might trigger implicit
755
        # deletion of newly created parents that could now become empty.
756
        self.record_delete(old_path, old_ie)
757
0.99.17 by Ian Clatworthy
Handle rename of a file/symlink modified already in this commit
758
        # Update the dictionaries used for tracking new file-ids
759
        if old_path in self._new_file_ids:
760
            del self._new_file_ids[old_path]
761
        else:
762
            del self._modified_file_ids[old_path]
0.99.6 by Ian Clatworthy
Handle rename of a just added file
763
        self._new_file_ids[new_path] = file_id
764
765
        # Create the new InventoryEntry
766
        kind = old_ie.kind
767
        basename, parent_id = self._ensure_directory(new_path,
768
            self.basis_inventory)
769
        ie = inventory.make_entry(kind, basename, parent_id, file_id)
770
        ie.revision = self.revision_id
771
        if kind == 'file':
772
            ie.executable = old_ie.executable
773
            ie.text_sha1 = old_ie.text_sha1
774
            ie.text_size = old_ie.text_size
775
        elif kind == 'symlink':
776
            ie.symlink_target = old_ie.symlink_target
777
778
        # Record it
779
        self.record_new(new_path, ie)
780
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
781
    def modify_handler(self, filecmd):
782
        if filecmd.dataref is not None:
0.64.229 by Ian Clatworthy
Handle git submodules in the stream by warning about + ignoring them
783
            if filecmd.kind == commands.TREE_REFERENCE_KIND:
784
                data = filecmd.dataref
785
            else:
786
                data = self.cache_mgr.fetch_blob(filecmd.dataref)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
787
        else:
788
            data = filecmd.data
789
        self.debug("modifying %s", filecmd.path)
790
        self._modify_item(filecmd.path, filecmd.kind,
0.81.7 by Ian Clatworthy
merge import tests and tweaks to make them pass
791
            filecmd.is_executable, data, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
792
793
    def delete_handler(self, filecmd):
794
        self.debug("deleting %s", filecmd.path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
795
        self._delete_item(filecmd.path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
796
797
    def copy_handler(self, filecmd):
798
        src_path = filecmd.src_path
799
        dest_path = filecmd.dest_path
800
        self.debug("copying %s to %s", src_path, dest_path)
0.81.10 by Ian Clatworthy
get DeltaCommitHandler passing all tests
801
        self._copy_item(src_path, dest_path, self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
802
803
    def rename_handler(self, filecmd):
804
        old_path = filecmd.old_path
805
        new_path = filecmd.new_path
806
        self.debug("renaming %s to %s", old_path, new_path)
807
        self._rename_item(old_path, new_path, self.basis_inventory)
808
809
    def deleteall_handler(self, filecmd):
810
        self.debug("deleting all files (and also all directories)")
811
        # I'm not 100% sure this will work in the delta case.
812
        # But clearing out the basis inventory so that everything
813
        # is added sounds ok in theory ...
814
        # We grab a copy as the basis is likely to be cached and
815
        # we don't want to destroy the cached version
0.84.3 by Ian Clatworthy
fix inventory copying when using deltas
816
        self.basis_inventory = copy_inventory(self.basis_inventory)
0.81.6 by Ian Clatworthy
basic DeltaCommitHandler mostly going bar rename
817
        self._delete_all_items(self.basis_inventory)