diff hgsubversion/hg_delta_editor.py @ 411:d71972428fce

editor: move current revision state into a separate class This makes it more obvious and needs fewer faux namespacing.
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Thu, 11 Jun 2009 10:12:16 +0200
parents eb524b957345
children ac0cc3c9ea63
line wrap: on
line diff
--- a/hgsubversion/hg_delta_editor.py
+++ b/hgsubversion/hg_delta_editor.py
@@ -65,6 +65,34 @@ def ieditor(fn):
     return fun
 
 
+class RevisionData(object):
+
+    __slots__ = [
+        'file', 'files', 'deleted', 'rev', 'execfiles', 'symlinks', 'batons',
+        'copies', 'missing', 'emptybranches', 'base', 'closebranches',
+        'externals',
+    ]
+
+    def __init__(self):
+        self.clear()
+
+    def clear(self):
+        self.file = None
+        self.files = {}
+        self.deleted = {}
+        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.emptybranches = {}
+        self.base = None
+        self.closebranches = set()
+        self.externals = {}
+
+
 class HgChangeReceiver(delta.Editor):
 
     def last_known_revision(self):
@@ -123,7 +151,7 @@ class HgChangeReceiver(delta.Editor):
         self.tag_locations.sort()
         self.tag_locations.reverse()
 
-        self.clear_current_info()
+        self.current = RevisionData()
         self.authors = maps.AuthorMap(self.ui, self.authors_file,
                                  defaulthost=author_host)
         if authors: self.authors.load(authors)
@@ -143,25 +171,6 @@ class HgChangeReceiver(delta.Editor):
             date = self.lastdate
         return date
 
-    def clear_current_info(self):
-        '''Clear the info relevant to a replayed revision so that the next
-        revision can be replayed.
-        '''
-        # Map files to raw svn data (symlink prefix is preserved)
-        self.current_files = {}
-        self.deleted_files = {}
-        self.current_rev = None
-        self.current_files_exec = {}
-        self.current_files_symlink = {}
-        self.dir_batons = {}
-        # Map fully qualified destination file paths to module source path
-        self.copies = {}
-        self.missing_plaintexts = set()
-        self.commit_branches_empty = {}
-        self.base_revision = None
-        self.branches_to_delete = set()
-        self.externals = {}
-
     def _save_metadata(self):
         '''Save the Subversion metadata. This should really be called after
         every revision is created.
@@ -230,28 +239,23 @@ class HgChangeReceiver(delta.Editor):
             return None, None, None
         return path, ln, test
 
-    def set_current_rev(self, rev):
-        """Set the revision we're currently converting.
-        """
-        self.current_rev = rev
-
     def set_file(self, path, data, isexec=False, islink=False):
         if islink:
             data = 'link ' + data
-        self.current_files[path] = data
-        self.current_files_exec[path] = isexec
-        self.current_files_symlink[path] = islink
-        if path in self.deleted_files:
-            del self.deleted_files[path]
-        if path in self.missing_plaintexts:
-            self.missing_plaintexts.remove(path)
+        self.current.files[path] = data
+        self.current.execfiles[path] = isexec
+        self.current.symlinks[path] = islink
+        if path in self.current.deleted:
+            del self.current.deleted[path]
+        if path in self.current.missing:
+            self.current.missing.remove(path)
 
     def delete_file(self, path):
-        self.deleted_files[path] = True
-        if path in self.current_files:
-            del self.current_files[path]
-        self.current_files_exec[path] = False
-        self.current_files_symlink[path] = False
+        self.current.deleted[path] = True
+        if path in self.current.files:
+            del self.current.files[path]
+        self.current.execfiles[path] = False
+        self.current.symlinks[path] = False
         self.ui.note('D %s\n' % path)
 
     def _normalize_path(self, path):
@@ -355,7 +359,7 @@ class HgChangeReceiver(delta.Editor):
         paths = revision.paths
         added_branches = {}
         added_tags = {}
-        self.branches_to_delete = set()
+        self.current.closebranches = set()
         tags_to_delete = set()
         for p in sorted(paths):
             t_name = self._is_path_tag(p)
@@ -408,7 +412,7 @@ class HgChangeReceiver(delta.Editor):
             if fi is not None:
                 if fi == '':
                     if paths[p].action == 'D':
-                        self.branches_to_delete.add(br) # case 4
+                        self.current.closebranches.add(br) # case 4
                     elif paths[p].action == 'R':
                         parent = self._determine_parent_branch(
                             p, paths[p].copyfrom_path, paths[p].copyfrom_rev,
@@ -418,7 +422,7 @@ class HgChangeReceiver(delta.Editor):
             if paths[p].action == 'D':
                 for known in self.branches:
                     if self._svnpath(known).startswith(p):
-                        self.branches_to_delete.add(known) # case 5
+                        self.current.closebranches.add(known) # case 5
             parent = self._determine_parent_branch(
                 p, paths[p].copyfrom_path, paths[p].copyfrom_rev, revision.revnum)
             if not parent and paths[p].copyfrom_path:
@@ -431,7 +435,7 @@ class HgChangeReceiver(delta.Editor):
         rmtags = dict((t, self.tags[t][0]) for t in tags_to_delete)
         return {
             'tags': (added_tags, rmtags),
-            'branches': (added_branches, self.branches_to_delete),
+            'branches': (added_branches, self.current.closebranches),
         }
 
     def save_tbdelta(self, tbdelta):
@@ -446,12 +450,12 @@ class HgChangeReceiver(delta.Editor):
         self.branches.update(tbdelta['branches'][0])
 
     def _updateexternals(self):
-        if not self.externals:
+        if not self.current.externals:
             return
         # Accumulate externals records for all branches
-        revnum = self.current_rev.revnum
+        revnum = self.current.rev.revnum
         branches = {}
-        for path, entry in self.externals.iteritems():
+        for path, entry in self.current.externals.iteritems():
             if not self._is_path_valid(path):
                 self.ui.warn('WARNING: Invalid path %s in externals\n' % path)
                 continue
@@ -536,19 +540,19 @@ class HgChangeReceiver(delta.Editor):
         if hasattr(self, '_exception_info'):  #pragma: no cover
             traceback.print_exception(*self._exception_info)
             raise ReplayException()
-        if self.missing_plaintexts:
+        if self.current.missing:
             raise MissingPlainTextError()
         self._updateexternals()
         # paranoidly generate the list of files to commit
-        files_to_commit = set(self.current_files.keys())
-        files_to_commit.update(self.current_files_symlink.keys())
-        files_to_commit.update(self.current_files_exec.keys())
-        files_to_commit.update(self.deleted_files.keys())
+        files_to_commit = set(self.current.files.keys())
+        files_to_commit.update(self.current.symlinks.keys())
+        files_to_commit.update(self.current.execfiles.keys())
+        files_to_commit.update(self.current.deleted.keys())
         # back to a list and sort so we get sane behavior
         files_to_commit = list(files_to_commit)
         files_to_commit.sort()
         branch_batches = {}
-        rev = self.current_rev
+        rev = self.current.rev
         date = self.fixdate(rev.date)
 
         # build up the branches that have files on them
@@ -572,13 +576,13 @@ class HgChangeReceiver(delta.Editor):
         # 1. handle normal commits
         closedrevs = closebranches.values()
         for branch, files in branch_batches.iteritems():
-            if branch in self.commit_branches_empty and files:
-                del self.commit_branches_empty[branch]
+            if branch in self.current.emptybranches and files:
+                del self.current.emptybranches[branch]
             files = dict(files)
 
             parents = (self.get_parent_revision(rev.revnum, branch),
                        revlog.nullid)
-            if parents[0] in closedrevs and branch in self.branches_to_delete:
+            if parents[0] in closedrevs and branch in self.current.closebranches:
                 continue
             extra = util.build_extra(rev.revnum, branch, self.uuid, self.subdir)
             if branch is not None:
@@ -588,20 +592,20 @@ class HgChangeReceiver(delta.Editor):
             parent_ctx = self.repo.changectx(parents[0])
             if '.hgsvnexternals' not in parent_ctx and '.hgsvnexternals' in files:
                 # Do not register empty externals files
-                if (files['.hgsvnexternals'] in self.current_files
-                    and not self.current_files[files['.hgsvnexternals']]):
+                if (files['.hgsvnexternals'] in self.current.files
+                    and not self.current.files[files['.hgsvnexternals']]):
                     del files['.hgsvnexternals']
 
             def filectxfn(repo, memctx, path):
                 current_file = files[path]
-                if current_file in self.deleted_files:
+                if current_file in self.current.deleted:
                     raise IOError()
-                copied = self.copies.get(current_file)
+                copied = self.current.copies.get(current_file)
                 flags = parent_ctx.flags(path)
-                is_exec = self.current_files_exec.get(current_file, 'x' in flags)
-                is_link = self.current_files_symlink.get(current_file, 'l' in flags)
-                if current_file in self.current_files:
-                    data = self.current_files[current_file]
+                is_exec = self.current.execfiles.get(current_file, 'x' in flags)
+                is_link = self.current.symlinks.get(current_file, 'l' in flags)
+                if current_file in self.current.files:
+                    data = self.current.files[current_file]
                     if is_link and data.startswith('link '):
                         data = data[len('link '):]
                     elif is_link:
@@ -629,7 +633,7 @@ class HgChangeReceiver(delta.Editor):
                 self.revmap[rev.revnum, branch] = new_hash
 
         # 2. handle branches that need to be committed without any files
-        for branch in self.commit_branches_empty:
+        for branch in self.current.emptybranches:
             ha = self.get_parent_revision(rev.revnum, branch)
             if ha == node.nullid:
                 continue
@@ -637,7 +641,7 @@ class HgChangeReceiver(delta.Editor):
             def del_all_files(*args):
                 raise IOError
             # True here meant nuke all files, shouldn't happen with branch closing
-            if self.commit_branches_empty[branch]: #pragma: no cover
+            if self.current.emptybranches[branch]: #pragma: no cover
                raise hgutil.Abort('Empty commit to an open branch attempted. '
                                   'Please report this issue.')
             extra = util.build_extra(rev.revnum, branch, self.uuid, self.subdir)
@@ -667,7 +671,7 @@ class HgChangeReceiver(delta.Editor):
             self.delbranch(branch, parent, rev)
 
         self._save_metadata()
-        self.clear_current_info()
+        self.current.clear()
 
     def delbranch(self, branch, node, rev):
         pctx = self.repo[node]
@@ -731,9 +735,9 @@ class HgChangeReceiver(delta.Editor):
     def delete_entry(self, path, revision_bogus, parent_baton, pool=None):
         br_path, branch = self._path_and_branch_for_path(path)
         if br_path == '':
-            self.branches_to_delete.add(branch)
+            self.current.closebranches.add(branch)
         if br_path is not None:
-            ha = self.get_parent_revision(self.current_rev.revnum, branch)
+            ha = self.get_parent_revision(self.current.rev.revnum, branch)
             if ha == revlog.nullid:
                 return
             ctx = self.repo.changectx(ha)
@@ -742,36 +746,36 @@ class HgChangeReceiver(delta.Editor):
                 if br_path != '':
                     br_path2 = br_path + '/'
                 # assuming it is a directory
-                self.externals[path] = None
-                map(self.delete_file, [pat for pat in self.current_files.iterkeys()
+                self.current.externals[path] = None
+                map(self.delete_file, [pat for pat in self.current.files.iterkeys()
                                        if pat.startswith(path+'/')])
                 for f in ctx.walk(util.PrefixMatch(br_path2)):
                     f_p = '%s/%s' % (path, f[len(br_path2):])
-                    if f_p not in self.current_files:
+                    if f_p not in self.current.files:
                         self.delete_file(f_p)
             self.delete_file(path)
 
     @ieditor
     def open_file(self, path, parent_baton, base_revision, p=None):
-        self.current_file = None
+        self.current.file = None
         fpath, branch = self._path_and_branch_for_path(path)
         if not fpath:
             self.ui.debug('WARNING: Opening non-existant file %s\n' % path)
             return
 
-        self.current_file = path
+        self.current.file = path
         self.ui.note('M %s\n' % path)
         if base_revision != -1:
-            self.base_revision = base_revision
+            self.current.base = base_revision
         else:
-            self.base_revision = None
+            self.current.base = None
 
-        if self.current_file in self.current_files:
+        if self.current.file in self.current.files:
             return
 
         baserev = base_revision
         if baserev is None or baserev == -1:
-            baserev = self.current_rev.revnum - 1
+            baserev = self.current.rev.revnum - 1
         parent = self.get_parent_revision(baserev + 1, branch)
 
         ctx = self.repo[parent]
@@ -779,7 +783,7 @@ class HgChangeReceiver(delta.Editor):
             return
 
         if fpath not in ctx:
-            self.missing_plaintexts.add(path)
+            self.current.missing.add(path)
 
         fctx = ctx.filectx(fpath)
         base = fctx.data()
@@ -790,17 +794,17 @@ class HgChangeReceiver(delta.Editor):
     @ieditor
     def add_file(self, path, parent_baton=None, copyfrom_path=None,
                  copyfrom_revision=None, file_pool=None):
-        self.current_file = None
-        self.base_revision = None
-        if path in self.deleted_files:
-            del self.deleted_files[path]
+        self.current.file = None
+        self.current.base = None
+        if path in self.current.deleted:
+            del self.current.deleted[path]
         fpath, branch = self._path_and_branch_for_path(path, existing=False)
         if not fpath:
             return
         if branch not in self.branches:
             # we know this branch will exist now, because it has at least one file. Rock.
-            self.branches[branch] = None, 0, self.current_rev.revnum
-        self.current_file = path
+            self.branches[branch] = None, 0, self.current.rev.revnum
+        self.current.file = path
         if not copyfrom_path:
             self.ui.note('A %s\n' % path)
             self.set_file(path, '', False, False)
@@ -809,7 +813,7 @@ class HgChangeReceiver(delta.Editor):
         (from_file,
          from_branch) = self._path_and_branch_for_path(copyfrom_path)
         if not from_file:
-            self.missing_plaintexts.add(path)
+            self.current.missing.add(path)
             return
         ha = self.get_parent_revision(copyfrom_revision + 1,
                                       from_branch)
@@ -819,23 +823,23 @@ class HgChangeReceiver(delta.Editor):
             flags = fctx.flags()
             self.set_file(path, fctx.data(), 'x' in flags, 'l' in flags)
         if from_branch == branch:
-            parentid = self.get_parent_revision(self.current_rev.revnum,
+            parentid = self.get_parent_revision(self.current.rev.revnum,
                                                 branch)
             if parentid != revlog.nullid:
                 parentctx = self.repo.changectx(parentid)
                 if util.aresamefiles(parentctx, ctx, [from_file]):
-                    self.copies[path] = from_file
+                    self.current.copies[path] = from_file
 
     @ieditor
     def add_directory(self, path, parent_baton, copyfrom_path,
                       copyfrom_revision, dir_pool=None):
-        self.dir_batons[path] = path
+        self.current.batons[path] = path
         br_path, branch = self._path_and_branch_for_path(path)
         if br_path is not None:
             if not copyfrom_path and not br_path:
-                self.commit_branches_empty[branch] = True
+                self.current.emptybranches[branch] = True
             else:
-                self.commit_branches_empty[branch] = False
+                self.current.emptybranches[branch] = False
         if br_path is None or not copyfrom_path:
             return path
         if copyfrom_path:
@@ -843,7 +847,7 @@ class HgChangeReceiver(delta.Editor):
             if tag not in self.tags:
                 tag = None
             if not self._is_path_valid(copyfrom_path) and not tag:
-                self.missing_plaintexts.add('%s/' % path)
+                self.current.missing.add('%s/' % path)
                 return path
         if tag:
             source_branch, source_rev = self.tags[tag]
@@ -853,11 +857,11 @@ class HgChangeReceiver(delta.Editor):
             cp_f, source_branch = self._path_and_branch_for_path(copyfrom_path)
             if cp_f == '' and br_path == '':
                 assert br_path is not None
-                self.branches[branch] = source_branch, source_rev, self.current_rev.revnum
+                self.branches[branch] = source_branch, source_rev, self.current.rev.revnum
         new_hash = self.get_parent_revision(source_rev + 1,
                                             source_branch)
         if new_hash == node.nullid:
-            self.missing_plaintexts.add('%s/' % path)
+            self.current.missing.add('%s/' % path)
             return path
         cp_f_ctx = self.repo.changectx(new_hash)
         if cp_f != '/' and cp_f != '':
@@ -872,64 +876,64 @@ class HgChangeReceiver(delta.Editor):
             fctx = cp_f_ctx.filectx(f)
             fp_c = path + '/' + f2
             self.set_file(fp_c, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags())
-            if fp_c in self.deleted_files:
-                del self.deleted_files[fp_c]
+            if fp_c in self.current.deleted:
+                del self.current.deleted[fp_c]
             if branch == source_branch:
                 copies[fp_c] = f
         if copies:
             # Preserve the directory copy records if no file was changed between
             # the source and destination revisions, or discard it completely.
-            parentid = self.get_parent_revision(self.current_rev.revnum, branch)
+            parentid = self.get_parent_revision(self.current.rev.revnum, branch)
             if parentid != revlog.nullid:
                 parentctx = self.repo.changectx(parentid)
                 if util.aresamefiles(parentctx, cp_f_ctx, copies.values()):
-                    self.copies.update(copies)
+                    self.current.copies.update(copies)
         return path
 
     @ieditor
     def change_file_prop(self, file_baton, name, value, pool=None):
         if name == 'svn:executable':
-            self.current_files_exec[self.current_file] = bool(value is not None)
+            self.current.execfiles[self.current.file] = bool(value is not None)
         elif name == 'svn:special':
-            self.current_files_symlink[self.current_file] = bool(value is not None)
+            self.current.symlinks[self.current.file] = bool(value is not None)
 
     @ieditor
     def change_dir_prop(self, dir_baton, name, value, pool=None):
         if dir_baton is None:
             return
-        path = self.dir_batons[dir_baton]
+        path = self.current.batons[dir_baton]
         if name == 'svn:externals':
-            self.externals[path] = value
+            self.current.externals[path] = value
 
     @ieditor
     def open_directory(self, path, parent_baton, base_revision, dir_pool=None):
-        self.dir_batons[path] = path
+        self.current.batons[path] = path
         p_, branch = self._path_and_branch_for_path(path)
         if p_ == '':
-            self.commit_branches_empty[branch] = False
+            self.current.emptybranches[branch] = False
         return path
 
     @ieditor
     def close_directory(self, dir_baton, dir_pool=None):
         if dir_baton is not None:
-            del self.dir_batons[dir_baton]
+            del self.current.batons[dir_baton]
 
     @ieditor
     def apply_textdelta(self, file_baton, base_checksum, pool=None):
         # We know coming in here the file must be one of the following options:
         # 1) Deleted (invalid, fail an assertion)
         # 2) Missing a base text (bail quick since we have to fetch a full plaintext)
-        # 3) Has a base text in self.current_files, apply deltas
+        # 3) Has a base text in self.current.files, apply deltas
         base = ''
-        if not self._is_path_valid(self.current_file):
+        if not self._is_path_valid(self.current.file):
             return lambda x: None
-        assert self.current_file not in self.deleted_files, (
-            'Cannot apply_textdelta to a deleted file: %s' % self.current_file)
-        assert (self.current_file in self.current_files
-                or self.current_file in self.missing_plaintexts), '%s not found' % self.current_file
-        if self.current_file in self.missing_plaintexts:
+        assert self.current.file not in self.current.deleted, (
+            'Cannot apply_textdelta to a deleted file: %s' % self.current.file)
+        assert (self.current.file in self.current.files
+                or self.current.file in self.current.missing), '%s not found' % self.current.file
+        if self.current.file in self.current.missing:
             return lambda x: None
-        base = self.current_files[self.current_file]
+        base = self.current.files[self.current.file]
         source = cStringIO.StringIO(base)
         target = cStringIO.StringIO()
         self.stream = target
@@ -940,19 +944,19 @@ class HgChangeReceiver(delta.Editor):
                                'cannot call handler!')
         def txdelt_window(window):
             try:
-                if not self._is_path_valid(self.current_file):
+                if not self._is_path_valid(self.current.file):
                     return
                 handler(window, baton)
                 # window being None means commit this file
                 if not window:
-                    self.current_files[self.current_file] = target.getvalue()
+                    self.current.files[self.current.file] = target.getvalue()
             except core.SubversionException, e: #pragma: no cover
                 if e.apr_err == core.SVN_ERR_INCOMPLETE_DATA:
-                    self.missing_plaintexts.add(self.current_file)
+                    self.current.missing.add(self.current.file)
                 else: #pragma: no cover
                     raise hgutil.Abort(*e.args)
             except: #pragma: no cover
-                print len(base), self.current_file
+                print len(base), self.current.file
                 self._exception_info = sys.exc_info()
                 raise
         return txdelt_window