You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by es...@apache.org on 2013/12/04 05:24:35 UTC
svn commit: r1547705 -
/subversion/trunk/contrib/server-side/svncutter/svncutter
Author: esr
Date: Wed Dec 4 04:24:35 2013
New Revision: 1547705
URL: http://svn.apache.org/r1547705
Log:
* contrib/server-side/svncutter/svncutter:
(doreduce): New subcommand for topologically reducing a dump.
Modified:
subversion/trunk/contrib/server-side/svncutter/svncutter
Modified: subversion/trunk/contrib/server-side/svncutter/svncutter
URL: http://svn.apache.org/viewvc/subversion/trunk/contrib/server-side/svncutter/svncutter?rev=1547705&r1=1547704&r2=1547705&view=diff
==============================================================================
--- subversion/trunk/contrib/server-side/svncutter/svncutter (original)
+++ subversion/trunk/contrib/server-side/svncutter/svncutter Wed Dec 4 04:24:35 2013
@@ -32,6 +32,7 @@ Available subcommands:
skeleton
expunge
renumber
+ reduce
"""
oneliners = {
@@ -45,6 +46,7 @@ oneliners = {
"skeleton": "Replace content with unique cookies, preserving structure",
"expunge": "Expunge operations by Node-path header",
"renumber": "Renumber revisions so they're contiguous",
+ "reduce": "Topologically reduce a dump.",
}
helpdict = {
@@ -146,6 +148,22 @@ renumber: usage: svncutter renumber
Renumber all revisions, patching Node-copyfrom headers as required.
Any selection option is ignored. Takes no arguments.
""",
+ "reduce": """\
+reduce: usage: svncutter reduce INPUT-FILE
+
+Strip revisions out of a dump so the only parts left those likely to
+be relevant to a conversion problem. A revision is interesting if it
+either (a) contains any operation that is not a plain file
+modification - any directory operation, or any add, or any delete, or
+any copy, or any operation on properties - or (b) it is referenced by
+a later copy operation. Any commit that is neither interesting nor
+has interesting neighbors is dropped.
+
+Because the 'interesting' status of a commit is not known for sure
+until all future commits have been checked for copy operations, this
+command requires an input file. It cannot operate on standard input.
+The reduced dump is emitted to standard output.
+""",
}
import os, sys, calendar, time, getopt, re
@@ -302,17 +320,20 @@ class DumpfileSource(LineBufferedSource)
if property_hook:
(props.propkeys, props.properties) = property_hook(props.propkeys, props.properties)
properties = str(props)
- content = ""
+ # use a list since extending the string gets slow
+ content_list = []
if "Text-content-length" in header:
while True:
line = self.readline()
#print "I see contents line", repr(line)
if not line:
break
- if line.startswith("Node-path:") or line.startswith("Revision-number"):
+ if line.startswith(("Node-path:", "Revision-number")):
self.push(line)
break
- content += line
+ content_list.append(line)
+ content = "".join(content_list)
+ del content_list
#print "READ NODE ENDS"
if property_hook:
header = DumpfileSource.set_length("Prop-content", header,
@@ -340,9 +361,9 @@ class DumpfileSource(LineBufferedSource)
oldrev = line.split()[1]
line = line.replace(oldrev, `revmap[int(oldrev)]`)
stash += line
- def report(self, selection, nodehook, prophook=None):
+ def report(self, selection, nodehook, prophook=None, passthrough=True):
"Report a filtered portion of content."
- emit = 0 in selection
+ emit = passthrough and 0 in selection
stash = self.read_until_next("Revision-number:")
if emit:
sys.stdout.write(stash)
@@ -363,12 +384,14 @@ class DumpfileSource(LineBufferedSource)
if not line:
return
elif line == '\n':
- sys.stdout.write(line)
+ if passthrough:
+ sys.stdout.write(line)
continue
elif line.startswith("Revision-number:"):
self.push(line)
if stash and nodecount == 0:
- sys.stdout.write(stash)
+ if passthrough:
+ sys.stdout.write(stash)
break
elif line.startswith("Node-path:"):
nodecount += 1
@@ -378,7 +401,8 @@ class DumpfileSource(LineBufferedSource)
if emit and stash:
emit = stash + emit
stash = ""
- sys.stdout.write(emit)
+ if passthrough:
+ sys.stdout.write(emit)
continue
else:
sys.stderr.write("svncutter: parse at %s doesn't look right (%s), aborting!\n" % (self.revision, repr(line)))
@@ -738,11 +762,13 @@ def skeletonize(source, selection, patte
# first check against the pattern
ok = True
- for pattern in patterns:
- if header.startswith("Node-path: ") and re.search(pattern, header[11:]):
- #sys.stderr.write("skeletonize skipping: " + header[11:header.index("\n")] +"\n")
- ok = False
- break
+ if header.startswith("Node-path: "):
+ filepath = header[11:header.index("\n")]
+ for pattern in patterns:
+ if re.search(pattern, filepath):
+ #sys.stderr.write("skeletonize skipping: %s\n" % filepath)
+ ok = False
+ break
if not ok:
return header + properties + content
del ok
@@ -766,13 +792,33 @@ def skeletonize(source, selection, patte
return header + properties + content
source.report(selection, __skeletonize)
+def doreduce(source):
+ "Topologically reduce a dump, removing spans of plain file modifications."
+ interesting = set([0])
+ def __reduce(header, properties, content):
+ def get_header(name):
+ m = re.search(name + ": (.*)", header)
+ return m and m.group(1)
+ if not (get_header("Node-kind") == "file" and get_header("Node-action") == "change") or properties:
+ interesting.add(source.revision)
+ copysource = get_header("Node-copyfrom-rev")
+ if copysource is not None:
+ interesting.add(int(copysource))
+ return None
+ source.report(SubversionRange("0:HEAD"), __reduce, passthrough=False)
+ selection = SubversionRange(",".join([str(x) for x in interesting or x+1 in interesting or x-1 in interesting]))
+ source.file.seek(0)
+ select(source, selection)
+
def expunge(source, selection, patterns):
"Strip out ops defined by a revision selection and a path regexp."
def __expunge(header, properties, content):
- for pattern in patterns:
- if header.startswith("Node-path: ") and re.search(pattern, header[11:]):
- #sys.stderr.write("expunge skipping: " + header[11:header.index("\n")] +"\n")
- return ""
+ if header.startswith("Node-path: "):
+ filepath = header[11:header.index("\n")]
+ for pattern in patterns:
+ if re.search(pattern, filepath):
+ #sys.stderr.write("expunge skipping: " + header[11:header.index("\n")] +"\n")
+ return ""
else:
return header + properties + content
source.report(selection, __expunge)
@@ -796,6 +842,9 @@ def renumber(source):
else:
sys.stdout.write(line)
+def patterns_compile(patterns):
+ return [re.compile(pattern) for pattern in patterns]
+
if __name__ == '__main__':
try:
(options, arguments) = getopt.getopt(sys.argv[1:], "ce:fl:m:p:qr:s",
@@ -855,11 +904,13 @@ if __name__ == '__main__':
sys.stderr.write("svncutter: setlog requires a log entries file.\n")
setlog(DumpfileSource(sys.stdin, baton), logpatch, selection)
elif arguments[0] == "skeleton":
- skeletonize(DumpfileSource(sys.stdin, baton), selection, arguments[1:])
+ skeletonize(DumpfileSource(sys.stdin, baton), selection, patterns_compile(arguments[1:]))
elif arguments[0] == "expunge":
- expunge(DumpfileSource(sys.stdin, baton), selection, arguments[1:])
+ expunge(DumpfileSource(sys.stdin, baton), selection, patterns_compile(arguments[1:]))
elif arguments[0] == "renumber":
renumber(DumpfileSource(sys.stdin, baton))
+ elif arguments[0] == "reduce":
+ doreduce(DumpfileSource(open(arguments[1]), baton))
elif arguments[0] == "help":
if len(arguments) == 1:
sys.stdout.write(__doc__)