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