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