def execute(self, inputfile, outputfile): """ Fix a cvs2svn created dump file. @type inputfile: string @param inputfile: Name of the cvs2svn created dump file. @type outputfile: string @param outputfile: Name of the fixed dump file. """ indump = SvnDumpFile() indump.open(inputfile) has_rev = indump.read_next_rev() outdump = SvnDumpFile() outdump.create_like(outputfile, indump) rc = 0 self.__history = {} while has_rev and rc == 0: outdump.add_rev(indump.get_rev_props()) for node in indump.get_nodes_iter(): msglist = self.__fix_node(indump.get_rev_nr(), node) if msglist != None: rc = 1 print "Error in r%d node %s:" % \ ( indump.get_rev_nr(), node.get_path() ) for msg in msglist: print " " + msg break outdump.add_node(node) has_rev = indump.read_next_rev() indump.close() return rc
def execute( self, inputfile, outputfile ): """ Fix a cvs2svn created dump file. @type inputfile: string @param inputfile: Name of the cvs2svn created dump file. @type outputfile: string @param outputfile: Name of the fixed dump file. """ indump = SvnDumpFile() indump.open( inputfile ) has_rev = indump.read_next_rev() outdump = SvnDumpFile() outdump.create_like( outputfile, indump ) rc = 0 self.__history = {} while has_rev and rc == 0: outdump.add_rev( indump.get_rev_props() ) for node in indump.get_nodes_iter(): msglist = self.__fix_node( indump.get_rev_nr(), node ) if msglist != None: rc = 1 print "Error in r%d node %s:" % \ ( indump.get_rev_nr(), node.get_path() ) for msg in msglist: print " " + msg break outdump.add_node( node ) has_rev = indump.read_next_rev() indump.close() return rc
class SvnDumpMerge: """ A class for merging svn dump files. """ # handle copyfrom-rev !!! def __init__(self): """ Initialize. """ # output file name self.__out_file = "" # date for revision 0 self.__out_r0_date = "9999-99-99T99:99:99.999999Z" # list additional directories self.__out_dirs = [] # log message for directory creating revision self.__out_message = "" # author for the additional revision self.__out_author = "svndumpmerge" # variables used for input dump files # file names self.__in_files = [] # path renames [ [ ( from, to ), ... ], ... ] self.__in_renames = [] # path regex substitutions [ [ ( search, replace ), ... ], ... ] self.__in_regex_subs = [] # mkdir excludes [ {}, ... ] self.__in_excludes = [] # revision number mappings [ {}, ... ] self.__in_rev_nr_maps = [] # dump files (class SvnDumpFile) self.__in_dumps = [] # revision dates of the dumps self.__in_rev_dates = [] def set_output_file(self, filename, startRev=0): """ Sets the output file name and optional start revision. @type filename: string @param filename: Name of the output dump file. @type startRev: integer @param startRev: Start revision number, default is 0. """ self.__out_file = filename self.outStartRev = startRev def add_input_file(self, filename): """ Adds an input file and returns it's index. @type filename: string @param filename: Name of a input dump file. @rtype: integer @return: Index of the input file. """ index = len(self.__in_files) self.__in_files = self.__in_files + [filename] self.__in_renames = self.__in_renames + [[]] self.__in_regex_subs = self.__in_regex_subs + [[]] self.__in_excludes = self.__in_excludes + [{}] self.__in_rev_nr_maps = self.__in_rev_nr_maps + [{}] return index def add_rename(self, index, prefixFrom, prefixTo): """ Adds a path prefix reanme. @type index: integer @param index: Index of the dump file. @type prefixFrom: string @param prefixFrom: From-path prefix (directory). @type prefixTo: string @param prefixTo: To-path prefix (directory). """ # make sure that prefixFrom starts and ends with a / if prefixFrom[0:1] == "/": prefixFrom = prefixFrom[1:] if prefixFrom[len(prefixFrom) - 1:] != "/": prefixFrom = prefixFrom + "/" # make sure that prefixTo starts and ends with a / if prefixTo[0:1] == "/": prefixTo = prefixTo[1:] if prefixTo[len(prefixTo) - 1:] != "/": prefixTo = prefixTo + "/" # add the rename self.__in_renames[index] = self.__in_renames[index] + \ [(prefixFrom, prefixTo)] def add_regex_sub(self, index, reSearch, reReplace): """ Adds a path prefix rename. @type index: integer @param index: Index of the dump file. @type reSearch: string @param reSearch: Search regular expression. @type reReplace: string @param reReplace: Replace regular expression. """ # compile regex object reSearch = re.compile(reSearch) # add the rename self.__in_regex_subs[index] = self.__in_regex_subs[index] + \ [(reSearch, reReplace)] def add_mkdir_exclude(self, index, dirName): """ Adds a mkdir exclude. @type index: integer @param index: Index of the dump file. @type dirName: string @param dirName: Name of the directory. """ # add the mkdir exclude self.__in_excludes[index][dirName] = None def add_directory(self, dirName): """ Adds an additional directory ('mkdir'). @type dirName: string @param dirName: Name of the directory. """ if dirName[0:1] == "/": dirName = dirName[1:] if dirName[-1:] == "/": dirName = dirName[:-1] self.__out_dirs = self.__out_dirs + [dirName] def set_log_message(self, msg): """ Set log message for additional dirs revision. @type msg: string @param msg: Log message. """ self.__out_message = msg def merge(self): """ Executes the merge. """ if len(self.__in_files) == 0: print("merge: no input files specified") return if len(self.__out_file) == 0: print("merge: no output file specified") return # open input dump files for inFile in self.__in_files: inDump = SvnDumpFile() inDump.open(inFile) inDump.read_next_rev() self.__in_dumps = self.__in_dumps + [inDump] if inDump.get_rev_date_str() < self.__out_r0_date: self.__out_r0_date = inDump.get_rev_date_str() # remove empty dumps dumpCount = self.__remove_empty_dumps() if dumpCount == 0: return # open output file self.outDump = SvnDumpFile() if self.outStartRev == 0: self.outDump.create_with_rev_0(self.__out_file, self.__in_dumps[0].get_uuid(), self.__out_r0_date) else: self.outDump.create_with_rev_n(self.__out_file, self.__in_dumps[0].get_uuid(), self.outStartRev) # skip revision 0 of all dumps for inDump in self.__in_dumps: if inDump.get_rev_nr() == 0: # +++ what about r0 revprops? inDump.read_next_rev() # remove empty dumps dumpCount = self.__remove_empty_dumps() if dumpCount == 0: self.outDump.close() return # get revision dates oldest = None oldestStr = "" for index in range(len(self.__in_dumps)): revDat = self.__in_dumps[index].get_rev_date() self.__in_rev_dates.append(revDat) if oldest is None or revDat < oldest: oldest = revDat oldestStr = self.__in_dumps[index].get_rev_date_str() # add additional directories if len(self.__out_dirs) > 0: self.outDump.add_rev({"svn:log": self.__out_message, "svn:author": self.__out_author, "svn:date": oldestStr}) for dirName in self.__out_dirs: node = SvnDumpNode(dirName, "add", "dir") self.outDump.add_node(node) # loop over all revisions while dumpCount > 0: # find index of the oldest revision oldestIndex = 0 for index in range(1, dumpCount): if self.__in_rev_dates[index] < self.__in_rev_dates[oldestIndex]: oldestIndex = index # copy revision self.__copy_revision(oldestIndex) print("Revision: %-8d from r%-8d %s" % (self.outDump.get_rev_nr(), self.__in_dumps[oldestIndex].get_rev_nr(), self.__in_files[oldestIndex])) # read next revision srcDump = self.__in_dumps[oldestIndex] if srcDump.read_next_rev(): self.__in_rev_dates[oldestIndex] = srcDump.get_rev_date() else: dumpCount = self.__remove_empty_dumps() # close output print("created %d revisions" % self.outDump.get_rev_nr()) self.outDump.close() def __copy_revision(self, dumpIndex): """ Copies a revision from inDump[dumpIndex] to outDump. @type dumpIndex: integer @param dumpIndex: Index of the input dump file. """ srcDump = self.__in_dumps[dumpIndex] # add revision and revprops self.outDump.add_rev(srcDump.get_rev_props()) # add nodes index = 0 nodeCount = srcDump.get_node_count() while index < nodeCount: node = srcDump.get_node(index) newNode = self.__change_node(dumpIndex, node) if newNode is not None: self.outDump.add_node(newNode) index = index + 1 # add revision info self.__in_rev_nr_maps[dumpIndex][srcDump.get_rev_nr()] = \ self.outDump.get_rev_nr() def __change_node(self, dumpIndex, node): """ Creates a new node if the path changed, else returns the old node. @type dumpIndex: integer @param dumpIndex: Index of the input dump file. @type node: SvnDumpNode @param node: A node. """ path = node.get_path() # mkdir exclude check if node.get_kind() == "dir" and node.get_action() == "add": if path in self.__in_excludes[dumpIndex]: return None fromPath = "" fromRev = 0 if node.has_copy_from(): fromPath = node.get_copy_from_path() fromRev = node.get_copy_from_rev() change = 0 newPath = self.__rename_path(path, dumpIndex) newFromPath = fromPath newFromRev = fromRev if path != newPath: change = 1 if fromRev > 0: newFromPath = self.__rename_path(fromPath, dumpIndex) if fromPath != newFromPath: change = 1 newFromRev = self.__in_rev_nr_maps[dumpIndex][fromRev] if fromRev != newFromRev: change = 1 newMergeInfo = "" if node.has_properties(): properties = node.get_properties() if properties.has_key('svn:mergeinfo'): mergeInfo = properties['svn:mergeinfo'] for line in mergeInfo.split('\n'): m = re.match('^(.*):(.*)', line) if m is not None: mergePath = m.group(1) revPart = m.group(2) newMergePath = self.__rename_path(mergePath, dumpIndex) if not newMergePath.startswith("/"): newMergePath = "/" + newMergePath if len(newMergeInfo) != 0: newMergeInfo = newMergeInfo + "\n" newMergeInfo = newMergeInfo + newMergePath + ":" revSep = "" for rm in re.finditer('(\d+)(?:-(\d+))?,?', revPart): mergeFrom = int(rm.group(1)) newMergeFrom = self.__in_rev_nr_maps[dumpIndex][mergeFrom] newMergeInfo = newMergeInfo + revSep + str(newMergeFrom) revSep = "," if rm.group(2) is not None: mergeTo = int(rm.group(2)) newMergeTo = self.__in_rev_nr_maps[dumpIndex][mergeTo] newMergeInfo = newMergeInfo + "-" + str(newMergeTo) if mergeInfo != newMergeInfo: change = 1 if not change: # no change needed return node # do the rename newNode = SvnDumpNode(newPath, node.get_action(), node.get_kind()) if node.has_copy_from(): newNode.set_copy_from(newFromPath, newFromRev) if node.has_properties(): newNode.set_properties(node.get_properties()) if len(newMergeInfo) > 0: newNode.set_property('svn:mergeinfo', newMergeInfo) if node.has_text(): newNode.set_text_node(node) return newNode def __rename_path(self, path, dumpIndex): """ Applies the renames to the path and returns the new path. @type path: string @param path: A path. @type renames: list( ( string, string ) ) @param renames: List of rename tuples. @rtype: string @return Renamed path. """ # ensure that path does not have a leading slash if len(path) > 1 and path[0:1] == "/": path = path[1:] sPath = path + "/" for sPfx, dPfx in self.__in_renames[dumpIndex]: sLen = len(sPfx) if sPfx == "/": return dPfx + path elif sPath[:sLen] == sPfx: if len(path) <= len(sPfx): # it's the full path return dPfx[0:len(dPfx) - 1] else: # there's a suffix return dPfx + path[sLen:] for reSearch, sReplace in self.__in_regex_subs[dumpIndex]: path = reSearch.sub(sReplace, path, count=1) return path def __remove_empty_dumps(self): """ Removes dump files which reached EOF and returns the count of dumps. @rtype: integer @return: Count of remaining input dump files. """ index = 0 while index < len(self.__in_dumps): inDump = self.__in_dumps[index] if inDump.has_revision(): index = index + 1 else: inDump.close() eidx = index + 1 self.__in_files[index:eidx] = [] self.__in_renames[index:eidx] = [] self.__in_regex_subs[index:eidx] = [] self.__in_excludes[index:eidx] = [] self.__in_rev_nr_maps[index:eidx] = [] self.__in_dumps[index:eidx] = [] self.__in_rev_dates[index:eidx] = [] return index
class SvnDumpMerge: """ A class for merging svn dump files. """ # handle copyfrom-rev !!! def __init__(self): """ Initialize. """ # output file name self.__out_file = "" # date for revision 0 self.__out_r0_date = "9999-99-99T99:99:99.999999Z" # list additional directories self.__out_dirs = [] # log message for directory creating revision self.__out_message = "" # author for the additional revision self.__out_author = "svndumpmerge" # variables used for input dump files # file names self.__in_files = [] # path renames [ [ ( from, to ), ... ], ... ] self.__in_renames = [] # path regex substitutions [ [ ( search, replace ), ... ], ... ] self.__in_regex_subs = [] # mkdir excludes [ {}, ... ] self.__in_excludes = [] # revision number mappings [ {}, ... ] self.__in_rev_nr_maps = [] # dump files (class SvnDumpFile) self.__in_dumps = [] # revision dates of the dumps self.__in_rev_dates = [] def set_output_file(self, filename, startRev=0): """ Sets the output file name and optional start revision. @type filename: string @param filename: Name of the output dump file. @type startRev: integer @param startRev: Start revision number, default is 0. """ self.__out_file = filename self.outStartRev = startRev def add_input_file(self, filename): """ Adds an input file and returns it's index. @type filename: string @param filename: Name of a input dump file. @rtype: integer @return: Index of the input file. """ index = len(self.__in_files) self.__in_files = self.__in_files + [filename] self.__in_renames = self.__in_renames + [[]] self.__in_regex_subs = self.__in_regex_subs + [[]] self.__in_excludes = self.__in_excludes + [{}] self.__in_rev_nr_maps = self.__in_rev_nr_maps + [{}] return index def add_rename(self, index, prefixFrom, prefixTo): """ Adds a path prefix reanme. @type index: integer @param index: Index of the dump file. @type prefixFrom: string @param prefixFrom: From-path prefix (directory). @type prefixTo: string @param prefixTo: To-path prefix (directory). """ # make sure that prefixFrom starts and ends with a / if prefixFrom[0:1] == "/": prefixFrom = prefixFrom[1:] if prefixFrom[len(prefixFrom) - 1:] != "/": prefixFrom = prefixFrom + "/" # make sure that prefixTo starts and ends with a / if prefixTo[0:1] == "/": prefixTo = prefixTo[1:] if prefixTo[len(prefixTo) - 1:] != "/": prefixTo = prefixTo + "/" # add the rename self.__in_renames[index] = self.__in_renames[index] + \ [ (prefixFrom, prefixTo ) ] def add_regex_sub(self, index, reSearch, reReplace): """ Adds a path prefix rename. @type index: integer @param index: Index of the dump file. @type reSearch: string @param reSearch: Search regular expression. @type reReplace: string @param reReplace: Replace regular expression. """ # compile regex object reSearch = re.compile(reSearch) # add the rename self.__in_regex_subs[index] = self.__in_regex_subs[index] + \ [ (reSearch, reReplace ) ] def add_mkdir_exclude(self, index, dirName): """ Adds a mkdir exclude. @type index: integer @param index: Index of the dump file. @type dirName: string @param dirName: Name of the directory. """ # add the mkdir exclude self.__in_excludes[index][dirName] = None def add_directory(self, dirName): """ Adds an additional directory ('mkdir'). @type dirName: string @param dirName: Name of the directory. """ if dirName[0:1] == "/": dirName = dirName[1:] if dirName[-1:] == "/": dirName = dirName[:-1] self.__out_dirs = self.__out_dirs + [dirName] def set_log_message(self, msg): """ Set log message for additional dirs revision. @type msg: string @param msg: Log message. """ self.__out_message = msg def merge(self): """ Executes the merge. """ if len(self.__in_files) == 0: print "merge: no input files specified" return if len(self.__out_file) == 0: print "merge: no output file specified" return # open input dump files for inFile in self.__in_files: inDump = SvnDumpFile() inDump.open(inFile) inDump.read_next_rev() self.__in_dumps = self.__in_dumps + [inDump] if inDump.get_rev_date_str() < self.__out_r0_date: self.__out_r0_date = inDump.get_rev_date_str() # remove empty dumps dumpCount = self.__remove_empty_dumps() if dumpCount == 0: return # open output file self.outDump = SvnDumpFile() if self.outStartRev == 0: self.outDump.create_with_rev_0(self.__out_file, self.__in_dumps[0].get_uuid(), self.__out_r0_date) else: self.outDump.create_with_rev_n(self.__out_file, self.__in_dumps[0].get_uuid(), self.outStartRev) # skip revision 0 of all dumps for inDump in self.__in_dumps: if inDump.get_rev_nr() == 0: # +++ what about r0 revprops? inDump.read_next_rev() # remove empty dumps dumpCount = self.__remove_empty_dumps() if dumpCount == 0: self.outDump.close() return # get revision dates oldest = None oldestStr = "" for index in range(len(self.__in_dumps)): revDat = self.__in_dumps[index].get_rev_date() self.__in_rev_dates.append(revDat) if oldest == None or revDat < oldest: oldest = revDat oldestStr = self.__in_dumps[index].get_rev_date_str() # add additional directories if len(self.__out_dirs) > 0: self.outDump.add_rev({ "svn:log": self.__out_message, "svn:author": self.__out_author, "svn:date": oldestStr }) for dirName in self.__out_dirs: node = SvnDumpNode(dirName, "add", "dir") self.outDump.add_node(node) # loop over all revisions while dumpCount > 0: # find index of the oldest revision oldestIndex = 0 for index in range(1, dumpCount): if self.__in_rev_dates[index] < self.__in_rev_dates[ oldestIndex]: oldestIndex = index # copy revision self.__copy_revision(oldestIndex) print "Revision: %-8d from r%-8d %s" % ( self.outDump.get_rev_nr(), self.__in_dumps[oldestIndex].get_rev_nr(), self.__in_files[oldestIndex]) # read next revision srcDump = self.__in_dumps[oldestIndex] if srcDump.read_next_rev(): self.__in_rev_dates[oldestIndex] = srcDump.get_rev_date() else: dumpCount = self.__remove_empty_dumps() # close output print "created %d revisions" % self.outDump.get_rev_nr() self.outDump.close() def __copy_revision(self, dumpIndex): """ Copies a revision from inDump[dumpIndex] to outDump. @type dumpIndex: integer @param dumpIndex: Index of the input dump file. """ srcDump = self.__in_dumps[dumpIndex] # add revision and revprops self.outDump.add_rev(srcDump.get_rev_props()) # add nodes index = 0 nodeCount = srcDump.get_node_count() while index < nodeCount: node = srcDump.get_node(index) newNode = self.__change_node(dumpIndex, node) if newNode != None: self.outDump.add_node(newNode) index = index + 1 # add revision info self.__in_rev_nr_maps[dumpIndex][srcDump.get_rev_nr()] = \ self.outDump.get_rev_nr() def __change_node(self, dumpIndex, node): """ Creates a new node if the path changed, else returns the old node. @type dumpIndex: integer @param dumpIndex: Index of the input dump file. @type node: SvnDumpNode @param node: A node. """ path = node.get_path() # mkdir exclude check if node.get_kind() == "dir" and node.get_action() == "add": if path in self.__in_excludes[dumpIndex]: return None fromPath = "" fromRev = 0 if node.has_copy_from(): fromPath = node.get_copy_from_path() fromRev = node.get_copy_from_rev() change = 0 newPath = self.__rename_path(path, dumpIndex) newFromPath = fromPath newFromRev = fromRev if path != newPath: change = 1 if fromRev > 0: newFromPath = self.__rename_path(fromPath, dumpIndex) if fromPath != newFromPath: change = 1 newFromRev = self.__in_rev_nr_maps[dumpIndex][fromRev] if fromRev != newFromRev: change = 1 newMergeInfo = "" if node.has_properties(): properties = node.get_properties() if properties.has_key('svn:mergeinfo'): mergeInfo = properties['svn:mergeinfo'] for line in mergeInfo.split('\n'): m = re.match('^(.*):(\d+)-(\d+)', line) if m != None: mergePath = m.group(1) mergeFrom = int(m.group(2)) mergeTo = int(m.group(3)) newMergePath = self.__rename_path(mergePath, dumpIndex) newMergeFrom = self.__in_rev_nr_maps[dumpIndex][ mergeFrom] newMergeTo = self.__in_rev_nr_maps[dumpIndex][mergeTo] if len(newMergeInfo) != 0: newMergeInfo = newMergeInfo + "\n" newMergeInfo = newMergeInfo + newMergePath + ":" + str( newMergeFrom) + "-" + str(newMergeTo) if mergeInfo != newMergeInfo: change = 1 if not change: # no change needed return node # do the rename newNode = SvnDumpNode(newPath, node.get_action(), node.get_kind()) if node.has_copy_from(): newNode.set_copy_from(newFromPath, newFromRev) if node.has_properties(): newNode.set_properties(node.get_properties()) if len(newMergeInfo) > 0: newNode.set_property('svn:mergeinfo', newMergeInfo) if node.has_text(): newNode.set_text_node(node) return newNode def __rename_path(self, path, dumpIndex): """ Applies the renames to the path and returns the new path. @type path: string @param path: A path. @type renames: list( ( string, string ) ) @param renames: List of rename tuples. @rtype: string @return Renamed path. """ # ensure that path does not have a leading slash if len(path) > 1 and path[0:1] == "/": path = path[1:] sPath = path + "/" for sPfx, dPfx in self.__in_renames[dumpIndex]: sLen = len(sPfx) if sPfx == "/": return dPfx + path elif sPath[:sLen] == sPfx: if len(path) <= len(sPfx): # it's the full path return dPfx[0:len(dPfx) - 1] else: # there's a suffix return dPfx + path[sLen:] for reSearch, sReplace in self.__in_regex_subs[dumpIndex]: path = reSearch.sub(sReplace, path, count=1) return path def __remove_empty_dumps(self): """ Removes dump files which reached EOF and returns the count of dumps. @rtype: integer @return: Count of remaining input dump files. """ index = 0 while index < len(self.__in_dumps): inDump = self.__in_dumps[index] if inDump.has_revision(): index = index + 1 else: inDump.close() eidx = index + 1 self.__in_files[index:eidx] = [] self.__in_renames[index:eidx] = [] self.__in_regex_subs[index:eidx] = [] self.__in_excludes[index:eidx] = [] self.__in_rev_nr_maps[index:eidx] = [] self.__in_dumps[index:eidx] = [] self.__in_rev_dates[index:eidx] = [] return index