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