def __replace_file(self, node, replacement_path): """ Replace the contents of a node. @type node: SvnDumpNode @param node: The node to replace the contents of. @type replacement_path: string @param replacement_path: The path to the replacement file. @rtype: SvnDumpNode @return: The converted node. """ if node.get_text_length() == -1: # no text self.__print(1, " no content to replace.") return node self.__print(1, " replace content with '%s'" % replacement_path) if self.dry_run: # do not replace with --dry-run return node # do the replacement newnode = SvnDumpNode(node.get_path(), node.get_action(), node.get_kind()) if node.has_copy_from(): newnode.set_copy_from(node.get_copy_from_path(), node.get_copy_from_rev()) if node.has_properties(): newnode.set_properties(node.get_properties()) newnode.set_text_file(replacement_path) return newnode
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 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 node.has_text(): newNode.set_text_node(node) return newNode
def copy_adding_git_ignore(srcfile, dstfile): """ Copy a dump file adding .gitignore files in all directories that have an svn:ignore property. @type srcfile: string @param srcfile: Source filename. @type dstfile: string @param dstfile: Destination filename. """ # SvnDumpFile classes for reading/writing dumps srcdmp = SvnDumpFile() dstdmp = SvnDumpFile() # open source file srcdmp.open(srcfile) gitignores = {} # used to ensure that copyfrom-revs are correct after the copy. If # there are any empty revision in the source dump file, the copyfrom-revs # could be affected. revmap = {} hasrev = srcdmp.read_next_rev() if hasrev: # create the dump file dstdmp.create_like(dstfile, srcdmp) # now copy all the revisions while hasrev: dstdmp.add_rev(srcdmp.get_rev_props()) for node in srcdmp.get_nodes_iter(): if node.has_copy_from(): node.set_copy_from(node.get_copy_from_path(), revmap[node.get_copy_from_rev()]) # add the original node dstdmp.add_node(node) if node.get_property("svn:ignore") is not None: # find out what the change is and act on it appropriately path = node.get_path() + "/.gitignore" action = node.get_action() if action == "change" or action == "add": if path in gitignores: # already saw this one - it is a change to the .gitignore file newnode = SvnDumpNode(path, "change", "file") else: # haven't seen this one yet newnode = SvnDumpNode(path, "add", "file") gitignores[path] = True f = open("gitignore", "wb") f.write(node.get_property("svn:ignore")) f.close() newnode.set_text_file("gitignore") dstdmp.add_node(newnode) os.remove("gitignore") elif action == "delete": newnode = SvnDumpNode(path, "delete", "file") dstdmp.add_node(newnode) del gitignores[path] else: print("Unhandled action: '%s'" % action) revmap[srcdmp.get_rev_nr()] = dstdmp.get_rev_nr() hasrev = srcdmp.read_next_rev() else: print("no revisions in the source dump '%s' ???" % srcfile) # cleanup srcdmp.close() dstdmp.close()
def read_next_rev( self ): """ Read the next revision. @rtype: bool @return: False if EOF occured. """ # check state if self.__state != self.ST_READ: raise SvnDumpException, "invalid state %d (should be %d)" % \ ( self.__state, self.ST_READ ) # check for end of file if self.__file_eof: self.__state = self.ST_EOF return False # go to start of revision if self.__rev_start_offset != self.__file.tell(): self.__file.seek( self.__rev_start_offset ) # get rev tags tags = self.__get_tag_list() self.__rev_nr = int( tags["Revision-number:"] ) # read revision properties self.__rev_props = self.__get_prop_list() self.__skip_empty_line() if not self.__rev_props.has_key("svn:log"): self.__rev_props["svn:log"] = "" if not self.__rev_props.has_key("svn:author"): self.__rev_props["svn:author"] = "" if self.__rev_props.has_key("svn:date"): self.set_rev_date(self.__rev_props["svn:date"] ) else: self.set_rev_date( "" ) # read nodes (files, dirs) self.__nodes.clear() #self.nodeList = [] tags = self.__get_tag_list() while len(tags) != 0: # check that it's not the next revision if tags.has_key( "Revision-number:" ): # go back to start of tag list self.__file.seek( self.__tag_start_offset ) self.__line_nr = self.__tag_start_line_nr break # get node properties if tags.has_key( "Prop-content-length:" ): properties = self.__get_prop_list() else: properties = None # skip node data if tags.has_key( "Text-content-length:" ): tags["Text-content-length:"] = int( tags["Text-content-length:"] ) offset = self.__file.tell() self.__skip_bin( tags["Text-content-length:"] ) self.__skip_empty_line() else: offset = 0 # add node path = tags["Node-path:"].lstrip('/') action = tags["Node-action:"] kind = "" if tags.has_key( "Node-kind:" ): kind = tags["Node-kind:"] node = SvnDumpNode( path, action, kind ) if properties != None: node.set_properties( properties ) if tags.has_key( "Node-copyfrom-path:" ): node.set_copy_from( tags["Node-copyfrom-path:"].lstrip('/'), int(tags["Node-copyfrom-rev:"]) ) if tags.has_key( "Text-content-md5:" ): md5 = tags["Text-content-md5:"] else: md5 = "" if tags.has_key( "Text-content-length:" ): node.set_text_fileobj( self.__file, offset, int(tags["Text-content-length:"]), md5 ) upath = ( action[0].upper(), path ) self.__nodes[upath] = node # next one... tags = self.__get_tag_list() self.__rev_start_offset = self.__file.tell() return True
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 read_next_rev(self): """ Read the next revision. @rtype: bool @return: False if EOF occured. """ # check state if self.__state != self.ST_READ: raise SvnDumpException("invalid state %d (should be %d)" % \ (self.__state, self.ST_READ)) # check for end of file if self.__file_eof: self.__state = self.ST_EOF return False # go to start of revision if self.__rev_start_offset != self.__file.tell(): self.__file.seek(self.__rev_start_offset) # get rev tags tags = self.__get_tag_list() self.__rev_nr = int(tags["Revision-number:"]) # read revision properties self.__rev_props = self.__get_prop_list() self.__skip_empty_line() if not self.__rev_props.has_key("svn:log"): self.__rev_props["svn:log"] = "" if not self.__rev_props.has_key("svn:author"): self.__rev_props["svn:author"] = "" if self.__rev_props.has_key("svn:date"): self.set_rev_date(self.__rev_props["svn:date"]) else: self.set_rev_date("") # read nodes (files, dirs) self.__nodes.clear() # self.nodeList = [] tags = self.__get_tag_list() while len(tags) != 0: # check that it's not the next revision if tags.has_key("Revision-number:"): # go back to start of tag list self.__file.seek(self.__tag_start_offset) self.__line_nr = self.__tag_start_line_nr break # get node properties if tags.has_key("Prop-content-length:"): properties = self.__get_prop_list() else: properties = None # skip node data if tags.has_key("Text-content-length:"): tags["Text-content-length:"] = int(tags["Text-content-length:"]) offset = self.__file.tell() self.__skip_bin(tags["Text-content-length:"]) self.__skip_empty_line() else: offset = 0 # add node path = tags["Node-path:"].lstrip('/') action = tags["Node-action:"] kind = "" if tags.has_key("Node-kind:"): kind = tags["Node-kind:"] node = SvnDumpNode(path, action, kind) if properties is not None: node.set_properties(properties) if tags.has_key("Node-copyfrom-path:"): node.set_copy_from(tags["Node-copyfrom-path:"].lstrip('/'), int(tags["Node-copyfrom-rev:"])) if tags.has_key("Text-content-md5:"): md5 = tags["Text-content-md5:"] else: md5 = "" if tags.has_key("Text-content-sha1:"): sha1 = tags["Text-content-sha1:"] else: sha1 = "" if tags.has_key("Text-content-length:"): node.set_text_fileobj(self.__file, offset, int(tags["Text-content-length:"]), md5, sha1) upath = (action[0].upper(), path) self.__nodes[upath] = node # next one... tags = self.__get_tag_list() self.__rev_start_offset = self.__file.tell() return True
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 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 __convert_eol(self, node, revnr): """ Convert EOL of a node. @type node: SvnDumpNode @param node: The node to convert. @type revnr: integer @param revnr: The current revision number. @rtype: SvnDumpNode @return: The converted node. """ if not node.has_text(): # no text print(" selected file, no text changes") return node # search for CR need_conv = False handle = node.text_open() data = node.text_read(handle) while len(data) > 0: if data.find("\r") != -1: # found one, need to convert the file need_conv = True break data = node.text_read(handle) fix = self.__fix if need_conv: # special fix option for rev/file ? key = (revnr, node.get_path()) if self.__fix_rev_path.has_key(key): fix = self.__fix_rev_path[key] print(" selected file, convert (fix option %d)" % fix) if self.__dry_run or fix == 0: # do not convert with --dry-run or when there's nothing to fix need_conv = False else: print(" selected file, no conversion required") if need_conv: # do the conversion node.text_reopen(handle) outfilename = self.__temp_file_name() outfile = open(outfilename, "wb") outlen = 0 md = sdt_md5() data = node.text_read(handle) carry = "" warning_printed = False while len(data) > 0: if len(carry) != 0: data = carry + data n = len(data) - 1 carry = data[n] if carry == "\r": data = data[:n] else: carry = "" if fix & 1 != 0: data = data.replace("\r\n", "\n") if fix & 2 != 0: data = data.replace("\r", "\n") if fix & 4 != 0: data = data.replace("\r", "") if not warning_printed and data.find("\r") >= 0: warning_printed = True print(" WARNING: file still contains CR") print(" file: '%s'" % node.get_path()) if self.__warning_file is not None: self.__warning_file.write( "# WARNING: file still contains CR\n") file = node.get_path() while file[0] == "/": file = file[1:] tmpfile = file.replace("/", "__") cmd = '$SVN cat -r %d "$REPOS/%s" > "%s"\n' % \ (revnr, node.get_path(), tmpfile) self.__warning_file.write(cmd) self.__warning_count += 1 md.update(data) outfile.write(data) outlen = outlen + len(data) data = node.text_read(handle) if len(carry) != 0: if fix & 2 != 0: carry = "\n" elif fix & 4 != 0: carry = "" outfile.write(carry) md.update(carry) outlen += len(carry) outfile.close() newnode = SvnDumpNode(node.get_path(), node.get_action(), node.get_kind()) if node.has_copy_from(): newnode.set_copy_from(node.get_copy_from_path(), node.get_copy_from_rev()) if node.has_properties(): newnode.set_properties(node.get_properties()) newnode.set_text_file(outfilename, outlen, md.hexdigest()) else: newnode = node node.text_close(handle) return newnode