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