# HG changeset patch # User Dirkjan Ochtman # Date 1244707936 -7200 # Node ID d71972428fce3b530d1eb8a9b67ad48f94d17dd4 # Parent eb524b95734525c88f8d1ead534836b4881d0a96 editor: move current revision state into a separate class This makes it more obvious and needs fewer faux namespacing. diff --git a/hgsubversion/cmdutil.py b/hgsubversion/cmdutil.py --- a/hgsubversion/cmdutil.py +++ b/hgsubversion/cmdutil.py @@ -56,16 +56,16 @@ def parentrev(ui, repo, hge, svn_commit_ def replay_convert_rev(ui, hg_editor, svn, r, tbdelta): # ui is only passed in for similarity with stupid.convert_rev() - hg_editor.set_current_rev(r) + hg_editor.current.rev = r hg_editor.save_tbdelta(tbdelta) # needed by get_replay() svn.get_replay(r.revnum, hg_editor) i = 1 - if hg_editor.missing_plaintexts: + if hg_editor.current.missing: hg_editor.ui.debug('Fetching %s files that could not use replay.\n' % - len(hg_editor.missing_plaintexts)) + len(hg_editor.current.missing)) files_to_grab = set() rootpath = svn.subdir and svn.subdir[1:] or '' - for p in hg_editor.missing_plaintexts: + for p in hg_editor.current.missing: hg_editor.ui.note('.') hg_editor.ui.flush() if p[-1] == '/': @@ -84,7 +84,7 @@ def replay_convert_rev(ui, hg_editor, sv i += 1 data, mode = svn.get_file(p, r.revnum) hg_editor.set_file(p, data, 'x' in mode, 'l' in mode) - hg_editor.missing_plaintexts = set() + hg_editor.current.missing = set() hg_editor.ui.note('\n') hg_editor.commit_current_delta(tbdelta) diff --git a/hgsubversion/hg_delta_editor.py b/hgsubversion/hg_delta_editor.py --- 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 diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -455,7 +455,7 @@ def branches_in_paths(hge, paths, revnum ln = hge._localname(p) # must check in branches_to_delete as well, because this runs after we # already updated the branch map - if ln in hge.branches or ln in hge.branches_to_delete: + if ln in hge.branches or ln in hge.current.closebranches: branches[ln] = p else: paths_need_discovery.append(p)