diff options
Diffstat (limited to 'bzr_commit_handler.py')
-rw-r--r-- | bzr_commit_handler.py | 865 |
1 files changed, 0 insertions, 865 deletions
diff --git a/bzr_commit_handler.py b/bzr_commit_handler.py deleted file mode 100644 index bd206bf..0000000 --- a/bzr_commit_handler.py +++ /dev/null @@ -1,865 +0,0 @@ -# Copyright (C) 2008 Canonical Ltd -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""CommitHandlers that build and save revisions & their inventories.""" - - -from bzrlib import ( - errors, - generate_ids, - inventory, - osutils, - revision, - serializer, - ) -from bzrlib.plugins.fastimport.fastimport import ( - commands, - helpers, - processor, - ) - - -_serializer_handles_escaping = hasattr(serializer.Serializer, - 'squashes_xml_invalid_characters') - - -def copy_inventory(inv): - # This currently breaks revision-id matching - #if hasattr(inv, "_get_mutable_inventory"): - # # TODO: Make this a public API on inventory - # return inv._get_mutable_inventory() - - # TODO: Shallow copy - deep inventory copying is expensive - return inv.copy() - - -class GenericCommitHandler(processor.CommitHandler): - """Base class for Bazaar CommitHandlers.""" - - def __init__(self, command, cache_mgr, rev_store, verbose=False, - prune_empty_dirs=True): - super(GenericCommitHandler, self).__init__(command) - self.cache_mgr = cache_mgr - self.rev_store = rev_store - self.verbose = verbose - self.branch_ref = command.ref - self.prune_empty_dirs = prune_empty_dirs - # This tracks path->file-id for things we're creating this commit. - # If the same path is created multiple times, we need to warn the - # user and add it just once. - # If a path is added then renamed or copied, we need to handle that. - self._new_file_ids = {} - # This tracks path->file-id for things we're modifying this commit. - # If a path is modified then renamed or copied, we need the make - # sure we grab the new content. - self._modified_file_ids = {} - # This tracks the paths for things we're deleting this commit. - # If the same path is added or the destination of a rename say, - # then a fresh file-id is required. - self._paths_deleted_this_commit = set() - - def pre_process_files(self): - """Prepare for committing.""" - self.revision_id = self.gen_revision_id() - # cache of texts for this commit, indexed by file-id - self.data_for_commit = {} - #if self.rev_store.expects_rich_root(): - self.data_for_commit[inventory.ROOT_ID] = [] - - # Track the heads and get the real parent list - parents = self.cache_mgr.track_heads(self.command) - - # Convert the parent commit-ids to bzr revision-ids - if parents: - self.parents = [self.cache_mgr.revision_ids[p] - for p in parents] - else: - self.parents = [] - self.debug("%s id: %s, parents: %s", self.command.id, - self.revision_id, str(self.parents)) - - # Tell the RevisionStore we're starting a new commit - self.revision = self.build_revision() - self.parent_invs = [self.get_inventory(p) for p in self.parents] - self.rev_store.start_new_revision(self.revision, self.parents, - self.parent_invs) - - # cache of per-file parents for this commit, indexed by file-id - self.per_file_parents_for_commit = {} - if self.rev_store.expects_rich_root(): - self.per_file_parents_for_commit[inventory.ROOT_ID] = () - - # Keep the basis inventory. This needs to be treated as read-only. - if len(self.parents) == 0: - self.basis_inventory = self._init_inventory() - else: - self.basis_inventory = self.get_inventory(self.parents[0]) - if hasattr(self.basis_inventory, "root_id"): - self.inventory_root_id = self.basis_inventory.root_id - else: - self.inventory_root_id = self.basis_inventory.root.file_id - - # directory-path -> inventory-entry for current inventory - self.directory_entries = {} - - def _init_inventory(self): - return self.rev_store.init_inventory(self.revision_id) - - def get_inventory(self, revision_id): - """Get the inventory for a revision id.""" - try: - inv = self.cache_mgr.inventories[revision_id] - except KeyError: - if self.verbose: - self.mutter("get_inventory cache miss for %s", revision_id) - # Not cached so reconstruct from the RevisionStore - inv = self.rev_store.get_inventory(revision_id) - self.cache_mgr.inventories[revision_id] = inv - return inv - - def _get_data(self, file_id): - """Get the data bytes for a file-id.""" - return self.data_for_commit[file_id] - - def _get_lines(self, file_id): - """Get the lines for a file-id.""" - return osutils.split_lines(self._get_data(file_id)) - - def _get_per_file_parents(self, file_id): - """Get the lines for a file-id.""" - return self.per_file_parents_for_commit[file_id] - - def _get_inventories(self, revision_ids): - """Get the inventories for revision-ids. - - This is a callback used by the RepositoryStore to - speed up inventory reconstruction. - """ - present = [] - inventories = [] - # If an inventory is in the cache, we assume it was - # successfully loaded into the revision store - for revision_id in revision_ids: - try: - inv = self.cache_mgr.inventories[revision_id] - present.append(revision_id) - except KeyError: - if self.verbose: - self.note("get_inventories cache miss for %s", revision_id) - # Not cached so reconstruct from the revision store - try: - inv = self.get_inventory(revision_id) - present.append(revision_id) - except: - inv = self._init_inventory() - self.cache_mgr.inventories[revision_id] = inv - inventories.append(inv) - return present, inventories - - def bzr_file_id_and_new(self, path): - """Get a Bazaar file identifier and new flag for a path. - - :return: file_id, is_new where - is_new = True if the file_id is newly created - """ - if path not in self._paths_deleted_this_commit: - # Try file-ids renamed in this commit - id = self._modified_file_ids.get(path) - if id is not None: - return id, False - - # Try the basis inventory - id = self.basis_inventory.path2id(path) - if id is not None: - return id, False - - # Try the other inventories - if len(self.parents) > 1: - for inv in self.parent_invs[1:]: - id = self.basis_inventory.path2id(path) - if id is not None: - return id, False - - # Doesn't exist yet so create it - dirname, basename = osutils.split(path) - id = generate_ids.gen_file_id(basename) - self.debug("Generated new file id %s for '%s' in revision-id '%s'", - id, path, self.revision_id) - self._new_file_ids[path] = id - return id, True - - def bzr_file_id(self, path): - """Get a Bazaar file identifier for a path.""" - return self.bzr_file_id_and_new(path)[0] - - def _format_name_email(self, name, email): - """Format name & email as a string.""" - if email: - return "%s <%s>" % (name, email) - else: - return name - - def gen_revision_id(self): - """Generate a revision id. - - Subclasses may override this to produce deterministic ids say. - """ - committer = self.command.committer - # Perhaps 'who' being the person running the import is ok? If so, - # it might be a bit quicker and give slightly better compression? - who = self._format_name_email(committer[0], committer[1]) - timestamp = committer[2] - return generate_ids.gen_revision_id(who, timestamp) - - def build_revision(self): - rev_props = self._legal_revision_properties(self.command.properties) - if 'branch-nick' not in rev_props: - rev_props['branch-nick'] = self.cache_mgr.branch_mapper.git_to_bzr( - self.branch_ref) - self._save_author_info(rev_props) - committer = self.command.committer - who = self._format_name_email(committer[0], committer[1]) - message = self.command.message - if not _serializer_handles_escaping: - # We need to assume the bad ol' days - message = helpers.escape_commit_message(message) - return revision.Revision( - timestamp=committer[2], - timezone=committer[3], - committer=who, - message=message, - revision_id=self.revision_id, - properties=rev_props, - parent_ids=self.parents) - - def _legal_revision_properties(self, props): - """Clean-up any revision properties we can't handle.""" - # For now, we just check for None because that's not allowed in 2.0rc1 - result = {} - if props is not None: - for name, value in props.items(): - if value is None: - self.warning( - "converting None to empty string for property %s" - % (name,)) - result[name] = '' - else: - result[name] = value - return result - - def _save_author_info(self, rev_props): - author = self.command.author - if author is None: - return - if self.command.more_authors: - authors = [author] + self.command.more_authors - author_ids = [self._format_name_email(a[0], a[1]) for a in authors] - elif author != self.command.committer: - author_ids = [self._format_name_email(author[0], author[1])] - else: - return - # If we reach here, there are authors worth storing - rev_props['authors'] = "\n".join(author_ids) - - def _modify_item(self, path, kind, is_executable, data, inv): - """Add to or change an item in the inventory.""" - # If we've already added this, warn the user that we're ignoring it. - # In the future, it might be nice to double check that the new data - # is the same as the old but, frankly, exporters should be fixed - # not to produce bad data streams in the first place ... - existing = self._new_file_ids.get(path) - if existing: - # We don't warn about directories because it's fine for them - # to be created already by a previous rename - if kind != 'directory': - self.warning("%s already added in this commit - ignoring" % - (path,)) - return - - # Create the new InventoryEntry - basename, parent_id = self._ensure_directory(path, inv) - file_id = self.bzr_file_id(path) - ie = inventory.make_entry(kind, basename, parent_id, file_id) - ie.revision = self.revision_id - if kind == 'file': - ie.executable = is_executable - # lines = osutils.split_lines(data) - ie.text_sha1 = osutils.sha_string(data) - ie.text_size = len(data) - self.data_for_commit[file_id] = data - elif kind == 'directory': - self.directory_entries[path] = ie - # There are no lines stored for a directory so - # make sure the cache used by get_lines knows that - self.data_for_commit[file_id] = '' - elif kind == 'symlink': - ie.symlink_target = data.encode('utf8') - # There are no lines stored for a symlink so - # make sure the cache used by get_lines knows that - self.data_for_commit[file_id] = '' - else: - self.warning("Cannot import items of kind '%s' yet - ignoring '%s'" - % (kind, path)) - return - # Record it - if file_id in inv: - old_ie = inv[file_id] - if old_ie.kind == 'directory': - self.record_delete(path, old_ie) - self.record_changed(path, ie, parent_id) - else: - try: - self.record_new(path, ie) - except: - print "failed to add path '%s' with entry '%s' in command %s" \ - % (path, ie, self.command.id) - print "parent's children are:\n%r\n" % (ie.parent_id.children,) - raise - - def _ensure_directory(self, path, inv): - """Ensure that the containing directory exists for 'path'""" - dirname, basename = osutils.split(path) - if dirname == '': - # the root node doesn't get updated - return basename, self.inventory_root_id - try: - ie = self._get_directory_entry(inv, dirname) - except KeyError: - # We will create this entry, since it doesn't exist - pass - else: - return basename, ie.file_id - - # No directory existed, we will just create one, first, make sure - # the parent exists - dir_basename, parent_id = self._ensure_directory(dirname, inv) - dir_file_id = self.bzr_file_id(dirname) - ie = inventory.entry_factory['directory'](dir_file_id, - dir_basename, parent_id) - ie.revision = self.revision_id - self.directory_entries[dirname] = ie - # There are no lines stored for a directory so - # make sure the cache used by get_lines knows that - self.data_for_commit[dir_file_id] = '' - - # It's possible that a file or symlink with that file-id - # already exists. If it does, we need to delete it. - if dir_file_id in inv: - self.record_delete(dirname, ie) - self.record_new(dirname, ie) - return basename, ie.file_id - - def _get_directory_entry(self, inv, dirname): - """Get the inventory entry for a directory. - - Raises KeyError if dirname is not a directory in inv. - """ - result = self.directory_entries.get(dirname) - if result is None: - if dirname in self._paths_deleted_this_commit: - raise KeyError - try: - file_id = inv.path2id(dirname) - except errors.NoSuchId: - # In a CHKInventory, this is raised if there's no root yet - raise KeyError - if file_id is None: - raise KeyError - result = inv[file_id] - # dirname must be a directory for us to return it - if result.kind == 'directory': - self.directory_entries[dirname] = result - else: - raise KeyError - return result - - def _delete_item(self, path, inv): - newly_added = self._new_file_ids.get(path) - if newly_added: - # We've only just added this path earlier in this commit. - file_id = newly_added - # note: delta entries look like (old, new, file-id, ie) - ie = self._delta_entries_by_fileid[file_id][3] - else: - file_id = inv.path2id(path) - if file_id is None: - self.mutter("ignoring delete of %s as not in inventory", path) - return - try: - ie = inv[file_id] - except errors.NoSuchId: - self.mutter("ignoring delete of %s as not in inventory", path) - return - self.record_delete(path, ie) - - def _copy_item(self, src_path, dest_path, inv): - newly_changed = self._new_file_ids.get(src_path) or \ - self._modified_file_ids.get(src_path) - if newly_changed: - # We've only just added/changed this path earlier in this commit. - file_id = newly_changed - # note: delta entries look like (old, new, file-id, ie) - ie = self._delta_entries_by_fileid[file_id][3] - else: - file_id = inv.path2id(src_path) - if file_id is None: - self.warning("ignoring copy of %s to %s - source does not exist", - src_path, dest_path) - return - ie = inv[file_id] - kind = ie.kind - if kind == 'file': - if newly_changed: - content = self.data_for_commit[file_id] - else: - content = self.rev_store.get_file_text(self.parents[0], file_id) - self._modify_item(dest_path, kind, ie.executable, content, inv) - elif kind == 'symlink': - self._modify_item(dest_path, kind, False, ie.symlink_target, inv) - else: - self.warning("ignoring copy of %s %s - feature not yet supported", - kind, path) - - def _rename_item(self, old_path, new_path, inv): - existing = self._new_file_ids.get(old_path) or \ - self._modified_file_ids.get(old_path) - if existing: - # We've only just added/modified this path earlier in this commit. - # Change the add/modify of old_path to an add of new_path - self._rename_pending_change(old_path, new_path, existing) - return - - file_id = inv.path2id(old_path) - if file_id is None: - self.warning( - "ignoring rename of %s to %s - old path does not exist" % - (old_path, new_path)) - return - ie = inv[file_id] - rev_id = ie.revision - new_file_id = inv.path2id(new_path) - if new_file_id is not None: - self.record_delete(new_path, inv[new_file_id]) - self.record_rename(old_path, new_path, file_id, ie) - - # The revision-id for this entry will be/has been updated and - # that means the loader then needs to know what the "new" text is. - # We therefore must go back to the revision store to get it. - lines = self.rev_store.get_file_lines(rev_id, file_id) - self.data_for_commit[file_id] = ''.join(lines) - - def _delete_all_items(self, inv): - for name, root_item in inv.root.children.iteritems(): - inv.remove_recursive_id(root_item.file_id) - - def _warn_unless_in_merges(self, fileid, path): - if len(self.parents) <= 1: - return - for parent in self.parents[1:]: - if fileid in self.get_inventory(parent): - return - self.warning("ignoring delete of %s as not in parent inventories", path) - - -class InventoryCommitHandler(GenericCommitHandler): - """A CommitHandler that builds and saves Inventory objects.""" - - def pre_process_files(self): - super(InventoryCommitHandler, self).pre_process_files() - - # Seed the inventory from the previous one. Note that - # the parent class version of pre_process_files() has - # already set the right basis_inventory for this branch - # but we need to copy it in order to mutate it safely - # without corrupting the cached inventory value. - if len(self.parents) == 0: - self.inventory = self.basis_inventory - else: - self.inventory = copy_inventory(self.basis_inventory) - self.inventory_root = self.inventory.root - - # directory-path -> inventory-entry for current inventory - self.directory_entries = dict(self.inventory.directories()) - - # Initialise the inventory revision info as required - if self.rev_store.expects_rich_root(): - self.inventory.revision_id = self.revision_id - else: - # In this revision store, root entries have no knit or weave. - # When serializing out to disk and back in, root.revision is - # always the new revision_id. - self.inventory.root.revision = self.revision_id - - def post_process_files(self): - """Save the revision.""" - self.cache_mgr.inventories[self.revision_id] = self.inventory - self.rev_store.load(self.revision, self.inventory, None, - lambda file_id: self._get_data(file_id), - lambda file_id: self._get_per_file_parents(file_id), - lambda revision_ids: self._get_inventories(revision_ids)) - - def record_new(self, path, ie): - try: - # If this is a merge, the file was most likely added already. - # The per-file parent(s) must therefore be calculated and - # we can't assume there are none. - per_file_parents, ie.revision = \ - self.rev_store.get_parents_and_revision_for_entry(ie) - self.per_file_parents_for_commit[ie.file_id] = per_file_parents - self.inventory.add(ie) - except errors.DuplicateFileId: - # Directory already exists as a file or symlink - del self.inventory[ie.file_id] - # Try again - self.inventory.add(ie) - - def record_changed(self, path, ie, parent_id): - # HACK: no API for this (del+add does more than it needs to) - per_file_parents, ie.revision = \ - self.rev_store.get_parents_and_revision_for_entry(ie) - self.per_file_parents_for_commit[ie.file_id] = per_file_parents - self.inventory._byid[ie.file_id] = ie - parent_ie = self.inventory._byid[parent_id] - parent_ie.children[ie.name] = ie - - def record_delete(self, path, ie): - self.inventory.remove_recursive_id(ie.file_id) - - def record_rename(self, old_path, new_path, file_id, ie): - # For a rename, the revision-id is always the new one so - # no need to change/set it here - ie.revision = self.revision_id - per_file_parents, _ = \ - self.rev_store.get_parents_and_revision_for_entry(ie) - self.per_file_parents_for_commit[file_id] = per_file_parents - new_basename, new_parent_id = self._ensure_directory(new_path, - self.inventory) - self.inventory.rename(file_id, new_parent_id, new_basename) - - def modify_handler(self, filecmd): - if filecmd.dataref is not None: - data = self.cache_mgr.fetch_blob(filecmd.dataref) - else: - data = filecmd.data - self.debug("modifying %s", filecmd.path) - self._modify_item(filecmd.path, filecmd.kind, - filecmd.is_executable, data, self.inventory) - - def delete_handler(self, filecmd): - self.debug("deleting %s", filecmd.path) - self._delete_item(filecmd.path, self.inventory) - - def copy_handler(self, filecmd): - src_path = filecmd.src_path - dest_path = filecmd.dest_path - self.debug("copying %s to %s", src_path, dest_path) - self._copy_item(src_path, dest_path, self.inventory) - - def rename_handler(self, filecmd): - old_path = filecmd.old_path - new_path = filecmd.new_path - self.debug("renaming %s to %s", old_path, new_path) - self._rename_item(old_path, new_path, self.inventory) - - def deleteall_handler(self, filecmd): - self.debug("deleting all files (and also all directories)") - self._delete_all_items(self.inventory) - - -class InventoryDeltaCommitHandler(GenericCommitHandler): - """A CommitHandler that builds Inventories by applying a delta.""" - - def pre_process_files(self): - super(InventoryDeltaCommitHandler, self).pre_process_files() - self._dirs_that_might_become_empty = set() - - # A given file-id can only appear once so we accumulate - # the entries in a dict then build the actual delta at the end - self._delta_entries_by_fileid = {} - if len(self.parents) == 0 or not self.rev_store.expects_rich_root(): - if self.parents: - old_path = '' - else: - old_path = None - # Need to explicitly add the root entry for the first revision - # and for non rich-root inventories - root_id = inventory.ROOT_ID - root_ie = inventory.InventoryDirectory(root_id, u'', None) - root_ie.revision = self.revision_id - self._add_entry((old_path, '', root_id, root_ie)) - - def post_process_files(self): - """Save the revision.""" - delta = self._get_final_delta() - inv = self.rev_store.load_using_delta(self.revision, - self.basis_inventory, delta, None, - self._get_data, - self._get_per_file_parents, - self._get_inventories) - self.cache_mgr.inventories[self.revision_id] = inv - #print "committed %s" % self.revision_id - - def _get_final_delta(self): - """Generate the final delta. - - Smart post-processing of changes, e.g. pruning of directories - that would become empty, goes here. - """ - delta = list(self._delta_entries_by_fileid.values()) - if self.prune_empty_dirs and self._dirs_that_might_become_empty: - candidates = self._dirs_that_might_become_empty - while candidates: - never_born = set() - parent_dirs_that_might_become_empty = set() - for path, file_id in self._empty_after_delta(delta, candidates): - newly_added = self._new_file_ids.get(path) - if newly_added: - never_born.add(newly_added) - else: - delta.append((path, None, file_id, None)) - parent_dir = osutils.dirname(path) - if parent_dir: - parent_dirs_that_might_become_empty.add(parent_dir) - candidates = parent_dirs_that_might_become_empty - # Clean up entries that got deleted before they were ever added - if never_born: - delta = [de for de in delta if de[2] not in never_born] - return delta - - def _empty_after_delta(self, delta, candidates): - #self.mutter("delta so far is:\n%s" % "\n".join([str(de) for de in delta])) - #self.mutter("candidates for deletion are:\n%s" % "\n".join([c for c in candidates])) - new_inv = self._get_proposed_inventory(delta) - result = [] - for dir in candidates: - file_id = new_inv.path2id(dir) - if file_id is None: - continue - ie = new_inv[file_id] - if ie.kind != 'directory': - continue - if len(ie.children) == 0: - result.append((dir, file_id)) - if self.verbose: - self.note("pruning empty directory %s" % (dir,)) - return result - - def _get_proposed_inventory(self, delta): - if len(self.parents): - # new_inv = self.basis_inventory._get_mutable_inventory() - # Note that this will create unreferenced chk pages if we end up - # deleting entries, because this 'test' inventory won't end up - # used. However, it is cheaper than having to create a full copy of - # the inventory for every commit. - new_inv = self.basis_inventory.create_by_apply_delta(delta, - 'not-a-valid-revision-id:') - else: - new_inv = inventory.Inventory(revision_id=self.revision_id) - # This is set in the delta so remove it to prevent a duplicate - del new_inv[inventory.ROOT_ID] - try: - new_inv.apply_delta(delta) - except errors.InconsistentDelta: - self.mutter("INCONSISTENT DELTA IS:\n%s" % "\n".join([str(de) for de in delta])) - raise - return new_inv - - def _add_entry(self, entry): - # We need to combine the data if multiple entries have the same file-id. - # For example, a rename followed by a modification looks like: - # - # (x, y, f, e) & (y, y, f, g) => (x, y, f, g) - # - # Likewise, a modification followed by a rename looks like: - # - # (x, x, f, e) & (x, y, f, g) => (x, y, f, g) - # - # Here's a rename followed by a delete and a modification followed by - # a delete: - # - # (x, y, f, e) & (y, None, f, None) => (x, None, f, None) - # (x, x, f, e) & (x, None, f, None) => (x, None, f, None) - # - # In summary, we use the original old-path, new new-path and new ie - # when combining entries. - old_path = entry[0] - new_path = entry[1] - file_id = entry[2] - ie = entry[3] - existing = self._delta_entries_by_fileid.get(file_id, None) - if existing is not None: - old_path = existing[0] - entry = (old_path, new_path, file_id, ie) - if new_path is None and old_path is None: - # This is a delete cancelling a previous add - del self._delta_entries_by_fileid[file_id] - parent_dir = osutils.dirname(existing[1]) - self.mutter("cancelling add of %s with parent %s" % (existing[1], parent_dir)) - if parent_dir: - self._dirs_that_might_become_empty.add(parent_dir) - return - else: - self._delta_entries_by_fileid[file_id] = entry - - # Collect parent directories that might become empty - if new_path is None: - # delete - parent_dir = osutils.dirname(old_path) - # note: no need to check the root - if parent_dir: - self._dirs_that_might_become_empty.add(parent_dir) - elif old_path is not None and old_path != new_path: - # rename - old_parent_dir = osutils.dirname(old_path) - new_parent_dir = osutils.dirname(new_path) - if old_parent_dir and old_parent_dir != new_parent_dir: - self._dirs_that_might_become_empty.add(old_parent_dir) - - # Calculate the per-file parents, if not already done - if file_id in self.per_file_parents_for_commit: - return - if old_path is None: - # add - # If this is a merge, the file was most likely added already. - # The per-file parent(s) must therefore be calculated and - # we can't assume there are none. - per_file_parents, ie.revision = \ - self.rev_store.get_parents_and_revision_for_entry(ie) - self.per_file_parents_for_commit[file_id] = per_file_parents - elif new_path is None: - # delete - pass - elif old_path != new_path: - # rename - per_file_parents, _ = \ - self.rev_store.get_parents_and_revision_for_entry(ie) - self.per_file_parents_for_commit[file_id] = per_file_parents - else: - # modify - per_file_parents, ie.revision = \ - self.rev_store.get_parents_and_revision_for_entry(ie) - self.per_file_parents_for_commit[file_id] = per_file_parents - - def record_new(self, path, ie): - self._add_entry((None, path, ie.file_id, ie)) - - def record_changed(self, path, ie, parent_id=None): - self._add_entry((path, path, ie.file_id, ie)) - self._modified_file_ids[path] = ie.file_id - - def record_delete(self, path, ie): - self._add_entry((path, None, ie.file_id, None)) - self._paths_deleted_this_commit.add(path) - if ie.kind == 'directory': - try: - del self.directory_entries[path] - except KeyError: - pass - for child_relpath, entry in \ - self.basis_inventory.iter_entries_by_dir(from_dir=ie): - child_path = osutils.pathjoin(path, child_relpath) - self._add_entry((child_path, None, entry.file_id, None)) - self._paths_deleted_this_commit.add(child_path) - if entry.kind == 'directory': - try: - del self.directory_entries[child_path] - except KeyError: - pass - - def record_rename(self, old_path, new_path, file_id, old_ie): - new_ie = old_ie.copy() - new_basename, new_parent_id = self._ensure_directory(new_path, - self.basis_inventory) - new_ie.name = new_basename - new_ie.parent_id = new_parent_id - new_ie.revision = self.revision_id - self._add_entry((old_path, new_path, file_id, new_ie)) - self._modified_file_ids[new_path] = file_id - self._paths_deleted_this_commit.discard(new_path) - if new_ie.kind == 'directory': - self.directory_entries[new_path] = new_ie - - def _rename_pending_change(self, old_path, new_path, file_id): - """Instead of adding/modifying old-path, add new-path instead.""" - # note: delta entries look like (old, new, file-id, ie) - old_ie = self._delta_entries_by_fileid[file_id][3] - - # Delete the old path. Note that this might trigger implicit - # deletion of newly created parents that could now become empty. - self.record_delete(old_path, old_ie) - - # Update the dictionaries used for tracking new file-ids - if old_path in self._new_file_ids: - del self._new_file_ids[old_path] - else: - del self._modified_file_ids[old_path] - self._new_file_ids[new_path] = file_id - - # Create the new InventoryEntry - kind = old_ie.kind - basename, parent_id = self._ensure_directory(new_path, - self.basis_inventory) - ie = inventory.make_entry(kind, basename, parent_id, file_id) - ie.revision = self.revision_id - if kind == 'file': - ie.executable = old_ie.executable - ie.text_sha1 = old_ie.text_sha1 - ie.text_size = old_ie.text_size - elif kind == 'symlink': - ie.symlink_target = old_ie.symlink_target - - # Record it - self.record_new(new_path, ie) - - def modify_handler(self, filecmd): - if filecmd.dataref is not None: - if filecmd.kind == commands.DIRECTORY_KIND: - data = None - elif filecmd.kind == commands.TREE_REFERENCE_KIND: - data = filecmd.dataref - else: - data = self.cache_mgr.fetch_blob(filecmd.dataref) - else: - data = filecmd.data - self.debug("modifying %s", filecmd.path) - self._modify_item(filecmd.path, filecmd.kind, - filecmd.is_executable, data, self.basis_inventory) - - def delete_handler(self, filecmd): - self.debug("deleting %s", filecmd.path) - self._delete_item(filecmd.path, self.basis_inventory) - - def copy_handler(self, filecmd): - src_path = filecmd.src_path - dest_path = filecmd.dest_path - self.debug("copying %s to %s", src_path, dest_path) - self._copy_item(src_path, dest_path, self.basis_inventory) - - def rename_handler(self, filecmd): - old_path = filecmd.old_path - new_path = filecmd.new_path - self.debug("renaming %s to %s", old_path, new_path) - self._rename_item(old_path, new_path, self.basis_inventory) - - def deleteall_handler(self, filecmd): - self.debug("deleting all files (and also all directories)") - # I'm not 100% sure this will work in the delta case. - # But clearing out the basis inventory so that everything - # is added sounds ok in theory ... - # We grab a copy as the basis is likely to be cached and - # we don't want to destroy the cached version - self.basis_inventory = copy_inventory(self.basis_inventory) - self._delete_all_items(self.basis_inventory) |