Patrick Mézard avatar Patrick Mézard committed 506dcb2

editor: tidy up directory batons handling

- Check they are opened in DFS order
- Do not make a special case for the root baton

Comments (0)

Files changed (1)

hgsubversion/editor.py

 class RevisionData(object):
 
     __slots__ = [
-        'file', 'added', 'deleted', 'rev', 'execfiles', 'symlinks', 'batons',
+        'file', 'added', 'deleted', 'rev', 'execfiles', 'symlinks',
         'copies', 'missing', 'emptybranches', 'base', 'externals', 'ui',
         'exception', 'store', '_failonmissing',
     ]
         self.rev = None
         self.execfiles = {}
         self.symlinks = {}
-        self.batons = {}
         # Map fully qualified destination file paths to module source path
         self.copies = {}
         self.missing = set()
         self._openpaths = {}
         self._deleted = set()
         self._getctx = util.lrucachefunc(self.repo.changectx, 3)
+        # A stack of opened directory (baton, path) pairs.
+        self._opendirs = []
 
     def _openfile(self, path, data, isexec, islink, copypath, create=False):
         if path in self._openpaths:
         if path in self._deleted:
             self._deleted.remove(path)
         self._filecounter += 1
-        baton = '%d-%s' % (self._filecounter, path)
+        baton = 'f%d-%s' % (self._filecounter, path)
         self._openfiles[baton] = (path, data, isexec, islink, copypath)
         self._openpaths[path] = baton
         return baton
 
+    def _opendir(self, path):
+        self._filecounter += 1
+        baton = 'f%d-%s' % (self._filecounter, path)
+        self._opendirs.append((baton, path))
+        return baton
+
+    def _checkparentdir(self, baton):
+        if not self._opendirs:
+            raise EditingError('trying to operate on an already closed '
+                'directory: %s' % baton)
+        if self._opendirs[-1][0] != baton:
+            raise EditingError('can only operate on the most recently '
+                'opened directory: %s != %s' % (self._opendirs[-1][0], baton))
+
     def _deletefile(self, path):
         self._deleted.add(path)
         if path in self._svncopies:
 
     @svnwrap.ieditor
     def delete_entry(self, path, revision_bogus, parent_baton, pool=None):
+        self._checkparentdir(parent_baton)
         br_path, branch = self.meta.split_branch_path(path)[:2]
         if br_path == '':
             if self.meta.get_path_tag(path):
 
     @svnwrap.ieditor
     def open_file(self, path, parent_baton, base_revision, p=None):
+        self._checkparentdir(parent_baton)
         fpath, branch = self.meta.split_branch_path(path)[:2]
         if not fpath:
             self.ui.debug('WARNING: Opening non-existant file %s\n' % path)
     @svnwrap.ieditor
     def add_file(self, path, parent_baton=None, copyfrom_path=None,
                  copyfrom_revision=None, file_pool=None):
+        self._checkparentdir(parent_baton)
         if path in self._svncopies:
             raise EditingError('trying to replace copied file %s' % path)
         if path in self._deleted:
     @svnwrap.ieditor
     def add_directory(self, path, parent_baton, copyfrom_path,
                       copyfrom_revision, dir_pool=None):
-        self.current.batons[path] = path
+        self._checkparentdir(parent_baton)
+        baton = self._opendir(path)
+
         br_path, branch = self.meta.split_branch_path(path)[:2]
         if br_path is not None:
             if not copyfrom_path and not br_path:
             else:
                 self.current.emptybranches[branch] = False
         if br_path is None or not copyfrom_path:
-            return path
+            return baton
         if self.meta.get_path_tag(path):
             del self.current.emptybranches[branch]
-            return path
+            return baton
         tag = self.meta.get_path_tag(copyfrom_path)
         if tag not in self.meta.tags:
             tag = None
                 # test it against the filemap. The actual path and
                 # revision will be resolved below if necessary.
                 self.current.addmissing('%s/' % path)
-                return path
+                return baton
         if tag:
             changeid = self.meta.tags[tag]
             source_rev, source_branch = self.meta.get_source_rev(changeid)[:2]
         new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
         if new_hash == node.nullid:
             self.current.addmissing('%s/' % path)
-            return path
+            return baton
         fromctx = self._getctx(new_hash)
         if frompath != '/' and frompath != '':
             frompath = '%s/' % frompath
             if pp.startswith(frompath):
                 dest = (path + '/' + pp[len(frompath):]).rstrip('/')
                 self.current.externals[dest] = v
-        return path
+        return baton
 
     @svnwrap.ieditor
     def change_file_prop(self, file_baton, name, value, pool=None):
 
     @svnwrap.ieditor
     def change_dir_prop(self, dir_baton, name, value, pool=None):
-        if dir_baton is None:
+        self._checkparentdir(dir_baton)
+        if len(self._opendirs) == 1:
             return
-        path = self.current.batons[dir_baton]
+        path = self._opendirs[-1][1]
         if name == 'svn:externals':
             self.current.externals[path] = value
 
         # We should not have to reset these, unfortunately the editor is
         # reused for different revisions.
         self._clear()
-        return None
+        return self._opendir('')
 
     @svnwrap.ieditor
     def open_directory(self, path, parent_baton, base_revision, dir_pool=None):
-        self.current.batons[path] = path
+        self._checkparentdir(parent_baton)
+        baton = self._opendir(path)
         p_, branch = self.meta.split_branch_path(path)[:2]
         if p_ == '' or (self.meta.layout == 'single' and p_):
             if not self.meta.get_path_tag(path):
                 self.current.emptybranches[branch] = False
-        return path
+        return baton
 
     @svnwrap.ieditor
     def close_directory(self, dir_baton, dir_pool=None):
-        if dir_baton is not None:
-            del self.current.batons[dir_baton]
+        self._checkparentdir(dir_baton)
+        self._opendirs.pop()
 
     @svnwrap.ieditor
     def apply_textdelta(self, file_baton, base_checksum, pool=None):
             raise EditingError('%d edited files were not closed'
                     % len(self._openfiles))
 
+        if self._opendirs:
+            raise EditingError('directory %s was not closed'
+                % self._opendirs[-1][1])
+
         # Resolve by changelog entries to avoid extra reads
         nodes = {}
         for path, copy in self._svncopies.iteritems():
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.