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