def update(self, revnum, id): """Indicate that the root node of this LOD changed to ID at REVNUM. REVNUM is a revision number that must be the same as that of the previous recorded change (in which case the previous change is overwritten) or later (in which the new change is appended). ID can be a node ID, or it can be None to indicate that this LOD ceased to exist in REVNUM.""" if revnum < self.revnums[-1]: raise KeyError() elif revnum == self.revnums[-1]: # This is an attempt to overwrite an entry that was already # updated during this revision. Don't allow the replacement # None -> None or allow one new id to be replaced with another: old_id = self.ids[-1] if old_id is None and id is None: raise InternalError( 'ID changed from None -> None for %s, r%d' % (self.lod, revnum,) ) elif (old_id is not None and id is not None and old_id in self._mirror._new_nodes): raise InternalError( 'ID changed from %x -> %x for %s, r%d' % (old_id, id, self.lod, revnum,) ) self.ids[-1] = id else: self.revnums.append(revnum) self.ids.append(id)
def get_range_map(self, svn_symbol_commit): """Return the ranges of all CVSSymbols in SVN_SYMBOL_COMMIT. Return a map { CVSSymbol : SVNRevisionRange }.""" # A map { cvs_symbol_id : CVSSymbol }: cvs_symbol_map = {} for cvs_symbol in svn_symbol_commit.get_cvs_items(): cvs_symbol_map[cvs_symbol.id] = cvs_symbol range_map = {} for (revnum, type, cvs_symbol_id) \ in self._generate_lines(svn_symbol_commit.symbol): cvs_symbol = cvs_symbol_map.get(cvs_symbol_id) if cvs_symbol is None: # This CVSSymbol is not part of SVN_SYMBOL_COMMIT. continue range = range_map.get(cvs_symbol) if type == OPENING: if range is not None: raise InternalError('Multiple openings logged for %r' % (cvs_symbol, )) range_map[cvs_symbol] = SVNRevisionRange( cvs_symbol.source_lod, revnum) else: if range is None: raise InternalError('Closing precedes opening for %r' % (cvs_symbol, )) if range.closing_revnum is not None: raise InternalError('Multiple closings logged for %r' % (cvs_symbol, )) range.add_closing(revnum) # Make sure that all CVSSymbols are accounted for, and adjust the # closings to be not later than svn_symbol_commit.revnum. for cvs_symbol in cvs_symbol_map.itervalues(): try: range = range_map[cvs_symbol] except KeyError: raise InternalError('No opening for %s' % (cvs_symbol, )) if range.opening_revnum >= svn_symbol_commit.revnum: raise InternalError('Opening in r%d not ready for %s in r%d' % ( range.opening_revnum, cvs_symbol, svn_symbol_commit.revnum, )) if range.closing_revnum is not None \ and range.closing_revnum > svn_symbol_commit.revnum: range.closing_revnum = None return range_map
def process_primary_commit(self, svn_commit): author = self._get_author(svn_commit) log_msg = self._get_log_msg(svn_commit) lods = set() for cvs_rev in svn_commit.get_cvs_items(): lods.add(cvs_rev.lod) if len(lods) != 1: raise InternalError('Commit affects %d LODs' % (len(lods),)) lod = lods.pop() self._mirror.start_commit(svn_commit.revnum) if isinstance(lod, Trunk): # FIXME: is this correct?: self.f.write('commit refs/heads/master\n') else: self.f.write('commit refs/heads/%s\n' % (lod.name,)) mark = self._create_commit_mark(lod, svn_commit.revnum) logger.normal( 'Writing commit r%d on %s (mark :%d)' % (svn_commit.revnum, lod, mark,) ) self.f.write('mark :%d\n' % (mark,)) self.f.write( 'committer %s %d +0000\n' % (author, svn_commit.date,) ) self.f.write('data %d\n' % (len(log_msg),)) self.f.write('%s\n' % (log_msg,)) for cvs_rev in svn_commit.get_cvs_items(): self.revision_writer.process_revision(cvs_rev, post_commit=False) self.f.write('\n') self._mirror.end_commit()
def copy_path(self, cvs_path, src_lod, dest_lod, src_revnum): if isinstance(cvs_path, CVSFile): node_kind = 'file' if cvs_path.rcs_basename == '.cvsignore': # FIXME: Here we have to adjust the containing directory's # svn:ignore property to reflect the addition of the # .cvsignore file to the LOD! This is awkward because we # don't have the contents of the .cvsignore file available. if not Ctx().keep_cvsignore: return elif isinstance(cvs_path, CVSDirectory): node_kind = 'dir' else: raise InternalError() self._dumpfile.write( 'Node-path: %s\n' 'Node-kind: %s\n' 'Node-action: add\n' 'Node-copyfrom-rev: %d\n' 'Node-copyfrom-path: %s\n' '\n' % ( self._utf8_path(dest_lod.get_path(cvs_path.cvs_path)), node_kind, src_revnum, self._utf8_path(src_lod.get_path(cvs_path.cvs_path)) ) )
def process_post_commit(self, svn_commit): author = self._get_author(svn_commit) log_msg = self._get_log_msg(svn_commit) source_lods = set() for cvs_rev in svn_commit.cvs_revs: source_lods.add(cvs_rev.lod) if len(source_lods) != 1: raise InternalError('Commit is from %d LODs' % (len(source_lods),)) source_lod = source_lods.pop() self._mirror.start_commit(svn_commit.revnum) # FIXME: is this correct?: self.f.write('commit refs/heads/master\n') mark = self._create_commit_mark(None, svn_commit.revnum) logger.normal( 'Writing post-commit r%d on Trunk (mark :%d)' % (svn_commit.revnum, mark,) ) self.f.write('mark :%d\n' % (mark,)) self.f.write( 'committer %s %d +0000\n' % (author, svn_commit.date,) ) self.f.write('data %d\n' % (len(log_msg),)) self.f.write('%s\n' % (log_msg,)) self.f.write( 'merge :%d\n' % (self._get_source_mark(source_lod, svn_commit.revnum),) ) for cvs_rev in svn_commit.cvs_revs: self.revision_writer.process_revision(cvs_rev, post_commit=True) self.f.write('\n') self._mirror.end_commit()
def _process_branch_changeset(self, changeset, timestamp): """Process BranchChangeset CHANGESET, producing a SVNBranchCommit. Filter out CVSBranchNoops. If no CVSBranches are left, don't generate a SVNBranchCommit.""" if Ctx().trunk_only: raise InternalError( 'BranchChangeset encountered during a --trunk-only conversion') cvs_branches = [ cvs_branch for cvs_branch in changeset.iter_cvs_items() if not isinstance(cvs_branch, CVSBranchNoop) ] if cvs_branches: svn_commit = SVNBranchCommit( changeset.symbol, [cvs_branch.id for cvs_branch in cvs_branches], timestamp, self.revnum_generator.gen_id(), ) yield svn_commit for cvs_branch in cvs_branches: Ctx()._symbolings_logger.log_branch_revision( cvs_branch, svn_commit.revnum) else: logger.debug( 'Omitting %r because it contains only CVSBranchNoops' % (changeset, ))
def iter_lods(self): """Iterate over LinesOfDevelopment in this file, in depth-first order. For each LOD, yield an LODItems instance. The traversal starts at each root node but returns the LODs in depth-first order. It is allowed to modify the CVSFileItems instance while the traversal is occurring, but only in ways that don't affect the tree structure above (i.e., towards the trunk from) the current LOD.""" # Make a list out of root_ids so that callers can change it: for id in list(self.root_ids): cvs_item = self[id] if isinstance(cvs_item, CVSRevision): # This LOD doesn't have a CVSBranch associated with it. # Either it is Trunk, or it is a branch whose CVSBranch has # been deleted. lod = cvs_item.lod cvs_branch = None elif isinstance(cvs_item, CVSBranch): # This is a Branch that has been severed from the rest of the # tree. lod = cvs_item.symbol id = cvs_item.next_id cvs_branch = cvs_item else: raise InternalError('Unexpected root item: %s' % (cvs_item, )) for lod_items in self._iter_tree(lod, cvs_branch, id): yield lod_items
def _is_simple_copy(self, svn_commit, source_groups): """Return True iff SVN_COMMIT can be created as a simple copy. SVN_COMMIT is an SVNTagCommit. Return True iff it can be created as a simple copy from an existing revision (i.e., if the fixup branch can be avoided for this tag creation).""" # The first requirement is that there be exactly one source: if len(source_groups) != 1: return False (svn_revnum, source_lod, cvs_symbols) = source_groups[0] # The second requirement is that the destination LOD not already # exist: try: self._mirror.get_current_lod_directory(svn_commit.symbol) except KeyError: # The LOD doesn't already exist. This is good. pass else: # The LOD already exists. It cannot be created by a copy. return False # The third requirement is that the source LOD contains exactly # the same files as we need to add to the symbol: try: source_node = self._mirror.get_old_lod_directory( source_lod, svn_revnum) except KeyError: raise InternalError('Source %r does not exist' % (source_lod, )) return (set([cvs_symbol.cvs_file for cvs_symbol in cvs_symbols ]) == set(self._get_all_files(source_node)))
def discard(self, *ids): """The text records with IDS are no longer needed; discard them. This involves calling their free() methods and also removing them from SELF. If SELF.deferred_deletes is not None, then the ids to be deleted are added to the list instead of deleted immediately. This mechanism is to prevent a stack overflow from the avalanche of deletes that can result from deleting a long chain of revisions.""" if self.deferred_deletes is None: # This is an outer-level delete. self.deferred_deletes = list(ids) while self.deferred_deletes: id = self.deferred_deletes.pop() text_record = self[id] if text_record.refcount != 0: raise InternalError( 'TextRecordDatabase.discard(%s) called with refcount = %d' % ( text_record, text_record.refcount, )) # This call might cause other text_record ids to be added to # self.deferred_deletes: text_record.free(self) del self[id] self.deferred_deletes = None else: self.deferred_deletes.extend(ids)
def process_post_commit(self, svn_commit): author = self._get_author(svn_commit) log_msg = self._get_log_msg(svn_commit) source_lods = set() for cvs_rev in svn_commit.cvs_revs: source_lods.add(cvs_rev.lod) if len(source_lods) != 1: raise InternalError('Commit is from %d LODs' % (len(source_lods), )) source_lod = source_lods.pop() # FIXME: is this correct?: self.f.write('commit refs/heads/master\n') self.f.write('mark :%d\n' % (self._create_commit_mark(None, svn_commit.revnum), )) self.f.write('committer %s %d +0000\n' % ( author, svn_commit.date, )) self.f.write('data %d\n' % (len(log_msg), )) self.f.write('%s\n' % (log_msg, )) self.f.write('merge :%d\n' % (self._get_source_mark(source_lod, svn_commit.revnum), )) for cvs_rev in svn_commit.cvs_revs: if isinstance(cvs_rev, CVSRevisionNoop): pass elif isinstance(cvs_rev, CVSRevisionDelete): self.f.write('D %s\n' % (cvs_rev.cvs_file.cvs_path, )) elif isinstance(cvs_rev, CVSRevisionModification): if cvs_rev.cvs_file.executable: mode = '100755' else: mode = '100644' self.f.write('M %s :%d %s\n' % ( mode, cvs_rev.revision_recorder_token, cvs_rev.cvs_file.cvs_path, )) else: raise InternalError('Unexpected CVSRevision type: %s' % (cvs_rev, )) self.f.write('\n')
def _set_symbol(self, symbol, mark): if isinstance(symbol, Branch): category = 'heads' elif isinstance(symbol, Tag): category = 'tags' else: raise InternalError() self.f.write('reset refs/%s/%s\n' % (category, symbol.name,)) self.f.write('from :%d\n' % (mark,))
def _process_symbol_commit(self, svn_commit, git_branch, source_groups): author = self._get_author(svn_commit) log_msg = self._get_log_msg(svn_commit) # There are two distinct cases we need to care for here: # 1. initial creation of a LOD # 2. fixup of an existing LOD to include more files, because the LOD in # CVS was created piecemeal over time, with intervening commits # We look at _marks here, but self._mirror._get_lod_history(lod).exists() # might be technically more correct (though _get_lod_history is currently # underscore-private) is_initial_lod_creation = svn_commit.symbol not in self._marks # Create the mark, only after the check above mark = self._create_commit_mark(svn_commit.symbol, svn_commit.revnum) if is_initial_lod_creation: # Get the primary parent p_source_revnum, p_source_lod, p_cvs_symbols = source_groups[0] try: p_source_node = self._mirror.get_old_lod_directory( p_source_lod, p_source_revnum ) except KeyError: raise InternalError('Source %r does not exist' % (p_source_lod,)) cvs_files_to_delete = set(self._get_all_files(p_source_node)) for (source_revnum, source_lod, cvs_symbols,) in source_groups: for cvs_symbol in cvs_symbols: cvs_files_to_delete.discard(cvs_symbol.cvs_file) self.f.write('commit %s\n' % (git_branch,)) self.f.write('mark :%d\n' % (mark,)) self.f.write('committer %s %d +0000\n' % (author, svn_commit.date,)) self.f.write('data %d\n' % (len(log_msg),)) self.f.write('%s\n' % (log_msg,)) # Only record actual DVCS ancestry for the primary sprout parent, # all the rest are effectively cherrypicks. if is_initial_lod_creation: self.f.write( 'from :%d\n' % (self._get_source_mark(p_source_lod, p_source_revnum),) ) for (source_revnum, source_lod, cvs_symbols,) in source_groups: for cvs_symbol in cvs_symbols: self.revision_writer.branch_file(cvs_symbol) if is_initial_lod_creation: for cvs_file in cvs_files_to_delete: self.f.write('D %s\n' % (cvs_file.cvs_path,)) self.f.write('\n') return mark
def process_revision(self, cvs_rev, post_commit): if isinstance(cvs_rev, CVSRevisionAdd): self.add_file(cvs_rev, post_commit) elif isinstance(cvs_rev, CVSRevisionChange): self.modify_file(cvs_rev, post_commit) elif isinstance(cvs_rev, CVSRevisionDelete): self.delete_file(cvs_rev, post_commit) elif isinstance(cvs_rev, CVSRevisionNoop): pass else: raise InternalError('Unexpected CVSRevision type: %s' % (cvs_rev,))
def create_symbol_changeset(id, symbol, cvs_item_ids): """Factory function for SymbolChangesets. Return a BranchChangeset or TagChangeset, depending on the type of SYMBOL. SYMBOL must be a Branch or Tag.""" if isinstance(symbol, Branch): return BranchChangeset(id, symbol, cvs_item_ids) if isinstance(symbol, Tag): return TagChangeset(id, symbol, cvs_item_ids) else: raise InternalError('Unknown symbol type %s' % (symbol, ))
def _adjust_branch_parents(self, cvs_branch): """Adjust the parent of CVS_BRANCH if possible and preferred. CVS_BRANCH is an instance of CVSBranch. This method must be called in leaf-to-trunk order.""" # The Symbol that cvs_branch would like to have as a parent: preferred_parent = Ctx()._symbol_db.get_symbol( cvs_branch.symbol.preferred_parent_id) if cvs_branch.source_lod == preferred_parent: # The preferred parent is already the parent. return # The CVSRevision that is its direct parent: source = self[cvs_branch.source_id] # This is always a CVSRevision because we haven't adjusted it yet: assert isinstance(source, CVSRevision) if isinstance(preferred_parent, Trunk): # It is not possible to graft *onto* Trunk: return # Try to find the preferred parent among the possible parents: for branch_id in source.branch_ids: possible_parent = self[branch_id] if possible_parent.symbol == preferred_parent: # We found it! break elif possible_parent.symbol == cvs_branch.symbol: # Only branches that precede the branch to be adjusted are # considered possible parents. Leave parentage unchanged: return else: # This point should never be reached. raise InternalError( 'Possible parent search did not terminate as expected') parent = possible_parent assert isinstance(parent, CVSBranch) Log().debug('Grafting %s from %s (on %s) onto %s' % ( cvs_branch, source, source.lod, parent, )) # Switch parent: source.branch_ids.remove(cvs_branch.id) parent.branch_ids.append(cvs_branch.id) cvs_branch.source_lod = parent.symbol cvs_branch.source_id = parent.id
def _get_metadata(self): """Return the Metadata instance for this commit.""" if self._metadata is None: # Set self._metadata for this commit from that of the first cvs # revision. if not self.cvs_revs: raise InternalError( 'SVNPrimaryCommit contains no CVS revisions') metadata_id = self.cvs_revs[0].metadata_id self._metadata = Ctx()._metadata_db[metadata_id] return self._metadata
def iter_root_lods(self): """Iterate over the LODItems for all root LODs (non-recursively).""" for id in list(self.root_ids): cvs_item = self[id] if isinstance(cvs_item, CVSRevision): # This LOD doesn't have a CVSBranch associated with it. # Either it is Trunk, or it is a branch whose CVSBranch has # been deleted. yield self._get_lod(cvs_item.lod, None, id) elif isinstance(cvs_item, CVSBranch): # This is a Branch that has been severed from the rest of the # tree. yield self._get_lod(cvs_item.symbol, cvs_item, cvs_item.next_id) else: raise InternalError('Unexpected root item: %s' % (cvs_item,))
def process_primary_commit(self, svn_commit): author = self._get_author(svn_commit) log_msg = self._get_log_msg(svn_commit) lods = set() for cvs_rev in svn_commit.get_cvs_items(): lods.add(cvs_rev.lod) if len(lods) != 1: raise InternalError('Commit affects %d LODs' % (len(lods), )) lod = lods.pop() if isinstance(lod, Trunk): # FIXME: is this correct?: self.f.write('commit refs/heads/master\n') else: self.f.write('commit refs/heads/%s\n' % (lod.name, )) self.f.write('mark :%d\n' % (self._create_commit_mark(lod, svn_commit.revnum), )) self.f.write('committer %s %d +0000\n' % ( author, svn_commit.date, )) self.f.write('data %d\n' % (len(log_msg), )) self.f.write('%s\n' % (log_msg, )) for cvs_rev in svn_commit.get_cvs_items(): if isinstance(cvs_rev, CVSRevisionNoop): pass elif isinstance(cvs_rev, CVSRevisionDelete): self.f.write('D %s\n' % (cvs_rev.cvs_file.cvs_path, )) elif isinstance(cvs_rev, CVSRevisionModification): if cvs_rev.cvs_file.executable: mode = '100755' else: mode = '100644' self.f.write('M %s :%d %s\n' % ( mode, cvs_rev.revision_recorder_token, cvs_rev.cvs_file.cvs_path, )) self.f.write('\n')
def _process_tag_changeset(self, changeset, timestamp): """Process TagChangeset CHANGESET, producing a SVNTagCommit. Filter out CVSTagNoops. If no CVSTags are left, don't generate a SVNTagCommit.""" if Ctx().trunk_only: raise InternalError( 'TagChangeset encountered during a --trunk-only conversion') cvs_tag_ids = [ cvs_tag.id for cvs_tag in changeset.iter_cvs_items() if not isinstance(cvs_tag, CVSTagNoop) ] if cvs_tag_ids: yield SVNTagCommit( changeset.symbol, cvs_tag_ids, timestamp, self.revnum_generator.gen_id(), ) else: logger.debug('Omitting %r because it contains only CVSTagNoops' % (changeset, ))
def _open_writable_node(self, cvs_directory, lod, create): """Open a writable node for CVS_DIRECTORY in LOD. Iff CREATE is True, create a directory node at SVN_PATH and any missing directories. Return an instance of _WritableMirrorNode. Raise KeyError if CVS_DIRECTORY doesn't exist and CREATE is not set.""" if cvs_directory.parent_directory is None: return self._open_writable_lod_node(lod, create) parent_node = self._open_writable_node(cvs_directory.parent_directory, lod, create) try: node = parent_node[cvs_directory] except KeyError: if create: # The component does not exist, so we create it. new_node = self._create_empty_node() parent_node[cvs_directory] = new_node self._invoke_delegates('mkdir', lod, cvs_directory) return new_node else: raise else: if isinstance(node, _WritableMirrorNode): return node elif isinstance(node, _ReadOnlyMirrorNode): new_node = self._copy_node(node) parent_node[cvs_directory] = new_node return new_node else: raise InternalError('Attempt to modify file at %s in mirror' % (cvs_directory, ))
def _set_node(self, cvs_file, svn_revision_range): parent_node = self._get_node(cvs_file.parent_directory, create=True) if cvs_file in parent_node: raise InternalError('%s appeared twice in sources for %s' % (cvs_file, self._symbol)) parent_node[cvs_file] = svn_revision_range
def __init__(self, symbol, cvs_symbol_ids, date, revnum): if not isinstance(symbol, Tag): raise InternalError('Incorrect symbol type %r' % (symbol, )) SVNSymbolCommit.__init__(self, symbol, cvs_symbol_ids, date, revnum)