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