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