Well, in the interests of not doing my homework, I've written some
more test code. We know can generate trees from entries files (of
either type) and then compare those trees against other kinds of
trees. I've written another test to use this to make sure the
checked out wc dir matches it's own entries files.
Attached is the huge diff against current test code, plus all of
svn_tree.py. There are a couple extra routines in there that aren't
used yet.
#!/usr/bin/env python
#
# svn_tree.py: tools for comparing directory trees
#
# Subversion is a tool for revision control.
# See http://subversion.tigris.org for more information.
#
# ====================================================================
# Copyright (c) 2001 Sam Tobin-Hochstadt. All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
######################################################################
class SVNTreeNode:
"""A node in the directory tree.
If CHILDREN is None, then the node is a file.
Otherwise, CHILDREN is a list of the nodes making up that
directorie's children.
NAME is simply the name of the file of directory"""
def __init__(self, name, children=None):
self.name = name
self.children = children
def __cmp__(self, other):
"Allows comparison on trees. Only compares the names."
# could this be rewritten as 'return
# self.name.__cmp__(other.name)' ??
if self.name == other.name:
return 0
if self.name > other.name:
return 1
else:
return -1
def add_child(self, newchild):
"""Add a child to a node. If the child already exists. merget
the new node's children with the current child node's
children. The merge is done recursively."""
if self.children is None:
self.children = []
n = 0
for a in self.children:
if a.name == newchild.name:
n = 1
break
if n == 0:
self.children.append(newchild)
elif not (newchild.children is None):
for i in newchild.children:
a.add_child(i)
# reserved name of the root of the tree
root_node_name = "__SVN_ROOT_NODE"
class SVNTreeError(Exception):
"Exception raised if you screw up in this module."
pass
class SVNTreeUnequal(Exception):
"Exception raised if two trees are unequal."
pass
########################################################################
# Code for handling creating trees from lists of pathnames. This will
# most likely be used for generating expected trees. Also useful for
# handling the output of SVN commands
#
def create_from_path(path):
"""Create and return a new tree, given a path representing a single
entry into that tree."""
# internal use only
# get a list of all the names in the path
# each of these will be a child of the former
elements = path.split("/")
if len(elements) == 0:
raise SVNTreeError
root_node = SVNTreeNode(elements[0], None)
add_elements_as_path(root_node, elements[1:])
return root_node
def add_elements_as_path(top_node, path_list):
"""Add the elements in PATH as if they were a single path below
TOP_NODE."""
# The idea of this function is to take a list like so:
# ['A', 'B', 'C'] and a top node, say 'Z', and generate a tree
# like this:
#
# Z -> A -> B -> C
#
# where 1 -> 2 means 2 is a child of 1.
#
# It's sort of hard to describe clearly, but it's quite useful.
prev_node = top_node
for i in path_list:
new_node = SVNTreeNode(i, None)
prev_node.add_child(new_node)
prev_node = new_node
def build_tree_from_paths(paths):
"Take PATHS and create a tree from them."
root = SVNTreeNode(root_node_name, None)
for i in paths:
root.add_child(create_from_path(i))
return root
#######################################################################
# Code for creating trees from the actual on-disk files. This is the
# most fundamental result, and (obviously) the most important for SVN
# to get right.
#
import os.path # lots of stuff
import os
def handle_dir(path, current_parent, ignore_cvs):
# get a list of all the files
all_files = os.listdir(path)
files = []
dirs = []
# put dirs and files in their own lists, and remove SVN dirs
for f in all_files:
f = os.path.join(path, f)
if (os.path.isdir(f) and os.path.basename(f) != 'SVN'
and (not(ignore_cvs) or os.path.basename(f) != 'CVS')):
dirs.append(f)
elif os.path.isfile(f):
files.append(f)
# add each file as a child of CURRENT_PARENT
for f in files:
current_parent.add_child(SVNTreeNode(os.path.basename(f)))
# for each subdir, create a node, walk its tree, add it as a child
for d in dirs:
new_dir_node = SVNTreeNode(os.path.basename(d))
handle_dir(d, new_dir_node, ignore_cvs)
current_parent.add_child(new_dir_node)
def build_tree_from_wc(wc_path, ignore_cvs=0):
"""Takes WC_PATH as the path to a working copy. Walks the tree below
that path, and creates the tree based on the actual found
files. If IGNORE_CVS is true, then exclude CVS dirs from the tree
as well as SVN dirs."""
root = SVNTreeNode(root_node_name, None)
# Walk the tree recursively
handle_dir(os.path.normpath(wc_path), root, ignore_cvs)
return root
########################################################################
# Code for creating trees from entries files on disk. It does the
# right thing, regardless of whether the entries files found are SVN or
# CVS. Just don't use it ones with both, or it will get very
# confused.
# ### samth todo -- fix this
# ### samth todo -- use the ancestor field, instead of the path
#
import svn_entry
def handle_entries_file(path, current_parent):
# get a dictionary of all the entries in the entries file
if os.path.exists(os.path.join(path, "SVN/entries")):
entry_path = os.path.join(path, "SVN/entries")
elif os.path.exists(os.path.join(path, "CVS/Entries")):
entry_path = os.path.join(path, "CVS/Entries")
entry_dict = svn_entry.get_entries(entry_path)
files = []
dirs = []
# put dirs and files in their own lists
for k in entry_dict.keys():
if entry_dict[k].atts['kind'] == 'file':
files.append(k)
elif (entry_dict[k].atts['kind'] == 'dir') and (k != ""):
dirs.append(k)
# add each file as a child of CURRENT_PARENT
for f in files:
current_parent.add_child(SVNTreeNode(f))
# for each subdir, create a node, walk its tree, add it as a child
for d in dirs:
new_dir_node = SVNTreeNode(d)
handle_entries_file(os.path.join(path, d), new_dir_node)
current_parent.add_child(new_dir_node)
def build_tree_from_entries(wc_path):
"""Takes WC_PATH as the path to a working copy. Walks the tree below
that path, and creates the tree based on the actual found
files."""
root = SVNTreeNode(root_node_name, None)
# Walk the tree recursively
handle_entries_file(os.path.normpath(wc_path), root)
return root
#######################################################################
# Code for handling trees generally.
#
def compare_trees(a, b):
"""Check whether A and B are the equal as trees. This comparison
does not take into account the order of children."""
try:
if a.name != b.name:
print "Error: %s is not the same as %s." % (a.name, b.name)
raise SVNTreeUnequal
if a.children is None:
if b.children is None:
# They are both files with the same name, and so are equal.
return 1
else:
# A is a file and B is a dir.
raise SVNTreeUnequal
if b.children is None:
# B is a file and A is a dir.
raise SVNTreeUnequal
# order of children doesn't matter
a.children.sort()
b.children.sort()
for i in range(max(len(a.children), len(b.children))):
compare_trees(a.children[i], b.children[i])
except IndexError:
# We ran off the end of one of the lists
print "Error: unequal number of children"
# Propogate the error upward
raise SVNTreeUnequal
except SVNTreeUnequal:
if a.name == root_node_name:
return 0
else:
# TODO Some nice pretty-printing of structure here
print "Unequal at node %s" % a.name
raise SVNTreeUnequal
return 1
def dump_tree(n,indent=""):
"Print out a nice representation of the tree."
# Code partially stolen from Dave Beazley
# We have to take the len(n.children), so we use a substitute.
if n.children is None:
tmp_children = []
else:
tmp_children = n.children
if n.name == root_node_name:
print "%s%s" % (indent, "Top of the tree")
else:
print "%s%s" % (indent, n.name)
# Shift the lines over
indent = indent.replace("-"," ")
indent = indent.replace("+"," ")
for i in range(len(tmp_children)):
c = tmp_children[i]
if i == len(tmp_children)-1:
# We're at the end of the list
dump_tree(c,indent + " +-- ")
else:
dump_tree(c,indent + " |-- ")
#########################################
## Test code -- to be removed
greek_paths = ['iota', 'A', 'A/mu', 'A/B', 'A/B/lambda', 'A/B/E',
'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/C', 'A/D',
'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
'A/D/H', 'A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega']
greek_paths2 = ['iota', 'A', 'A/mu', 'A/B', 'A/B/lambda', 'A/B/E',
'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/C', 'A/D',
'A/D/H', 'A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega']
greek_paths3 = ['iota', 'A', 'A/mu', 'A/B', 'A/B/lambda', 'A/B/E',
'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/C', 'A/D',
'A/D/H', 'A/D/H/chi', 'A/D/H/psi', 'A/D/H/omegafoo']
if __name__ == '__main__':
# We were invoked on the command line
test_tree = build_tree_from_paths(greek_paths)
test_tree2 = build_tree_from_paths(greek_paths2)
test_tree3 = build_tree_from_paths(greek_paths3)
dump_tree(test_tree)
dump_tree(test_tree2)
if 1 == compare_trees(test_tree, test_tree3):
print "they are equal"
else:
print "they are not equal"
### End of file.
# local variables:
# eval: (load-file "../../../svn-dev.el")
# end:
Index: svn_entry.py
===================================================================
RCS file: /cvs/subversion/subversion/tests/clients/cmdline/svn_entry.py,v
retrieving revision 1.4
diff -u -r1.4 svn_entry.py
--- svn_entry.py 2001/04/12 18:11:02 1.4
+++ svn_entry.py 2001/04/20 13:00:21
@@ -26,12 +26,13 @@
# kind, revision, ancestor. Other optional keys *might* be
# present, such as prop-time, text-time, add, delete, conflict.
-import xml.parsers.expat # you may need to install this package
+import xml.parsers.expat # you may need to install this package
+import copy # for deepcopy
class svn_entry:
"An object that represents an entry from an 'entries' file."
- def __init__(self, attributes): # constructor
+ def __init__(self, attributes={}): # constructor
self.atts = attributes
def prettyprint(self):
@@ -50,6 +51,25 @@
self.parser = xml.parsers.expat.ParserCreate()
self.parser.StartElementHandler = self.handle_start_tag
+ def is_svn_entries(self, file):
+ "Determine if FILE is a CVS or an SVN entries file."
+
+ # read the first line, and strip off whitespace
+ first_line = file.readline().strip()
+
+ # strip of any blank lines at the beginning
+ while first_line[0] == '\n':
+ first_line = file.readline().strip()
+
+ # go back to the beginning, so that parsing will work properly
+ file.seek(0)
+
+ if first_line.find("xml") != -1:
+ return 1
+
+ elif (first_line[0] == '/') or (first_line[0] == 'D'):
+ return 0
+
def handle_start_tag(self, name, attrs):
"Expat callback that receives a new open-tag."
@@ -71,14 +91,73 @@
self.entry_dict[attrs['name']] = entry # store the new entry
+ def svn_parse_file(self, file):
+ self.parser.ParseFile(file)
+
+ def cvs_parse_file(self, file):
+ "Parse the CVS 'Entries' file FILE into a dictionary."
+
+ # Create a parent entry to represent the current directory
+ # This directory isn't in the Entries file, and isn't really under
+ # CVS's control, but SVN has an entry for it, and we want to be
+ # compatible
+ parent = svn_entry({})
+ parent.atts['name'] = "" # for consistency with subversion
+ # this is arbitary, since it isn't versioned by CVS
+ parent.atts['revision'] = "1"
+ # FIXME This is wrong. How can we get this info? Do we want to
+ # fix this here, or when we create the tree? Doing it here
+ # requires info not in the Entries file
+ parent.atts['ancestor'] = "anni"
+ parent.atts['kind'] = "dir"
+ self.entry_dict[parent.atts['name']] = parent
+
+ lines = file.readlines()
+ for l in lines:
+ entry = svn_entry()
+ if l[0] == 'D': # Directories begin with D
+ if len(l) == 1:
+ # If a directory contains no subdirectories, the last line
+ # of the entries file is a single 'D'
+ continue
+ else:
+ entry.atts['kind'] = 'dir'
+ l = l[1:]
+ else:
+ entry.atts['kind'] = 'file'
+ props = l.split("/")
+ if l[0] == '\n': # skip empty entries
+ continue
+ entry.atts['name'] = props[1]
+ entry.atts['revision'] = props[2]
+ entry.atts['text-time'] = props[3]
+ entry.atts['ancestor'] = parent.atts['ancestor'] + '/' \
+ + entry.atts['name']
+
+ self.entry_dict[entry.atts['name']] = copy.deepcopy(entry) # store the new entry
+
+ def parse_file(self, file):
+ if self.is_svn_entries(file):
+ self.svn_parse_file(file)
+ else:
+ self.cvs_parse_file(file)
+
+ def prettyprint(self):
+ "Pretty-print the entire set of entries"
+ for i in self.entry_dict.values():
+ i.prettyprint()
+
+
# The main exported routine
def get_entries(path):
- "Parse the entries file at PATH and return a list of svn_entry objects."
+ """Parse the entries file at PATH and return a dictionary of entry
+ objects. This can be passed either a CVS or an SVN entries file,
+ and it will do the right thing."""
- entryparser = svn_entryparser() # make a parser instance
+ entryparser = svn_entryparser()
fp = open(path, 'r')
- entryparser.parser.ParseFile(fp)
+ entryparser.parse_file(fp)
fp.close()
return entryparser.entry_dict
Index: svn_test_main.py
===================================================================
RCS file: /cvs/subversion/subversion/tests/clients/cmdline/svn_test_main.py,v
retrieving revision 1.4
diff -u -r1.4 svn_test_main.py
--- svn_test_main.py 2001/04/16 01:13:40 1.4
+++ svn_test_main.py 2001/04/20 13:00:21
@@ -43,6 +43,8 @@
# Global: set this to the location of the svn binary
svn_binary = '../../../client/svn'
+# Global: set this to the location of the svnadmin binary
+svnadmin_binary = '../../../svnadmin/svnadmin'
######################################################################
# Utilities shared by the tests
@@ -57,7 +59,16 @@
infile, outfile = os.popen2(command) # run command, get 2 file descriptors
return outfile.readlines() # convert stdout to list of lines
+# For running svnadmin. Ignores the output.
+def run_svnadmin(*varargs):
+ "Run svnadmin with VARARGS."
+ command = svnadmin_binary
+ for arg in varargs:
+ command = command + " " + `arg` # build the command string
+ os.popen2(command) # run command
+
+
# For clearing away working copies
def remove_wc(dirname):
"Remove a working copy named DIRNAME."
@@ -72,6 +83,15 @@
fp = open(path, 'a') # open in (a)ppend mode
fp.write(new_text)
fp.close()
+
+# For creating blank new repositories
+def create_repos(path):
+ """Create a brand-new SVN repository at PATH. If PATH does not yet
+ exist, create it."""
+
+ if not(os.path.exists(path)):
+ os.makedirs(path) # this creates all the intermediate dirs, if neccessary
+ run_svnadmin("create", path)
# -- put more shared routines here --
Index: xml_tests.py
===================================================================
RCS file: /cvs/subversion/subversion/tests/clients/cmdline/xml_tests.py,v
retrieving revision 1.2
diff -u -r1.2 xml_tests.py
--- xml_tests.py 2001/04/16 22:18:01 1.2
+++ xml_tests.py 2001/04/20 13:00:21
@@ -158,16 +158,63 @@
return 0 # final success
+#-----------------------------------------------------------------------------
+def xml_test_3():
+ """verify the actual wc from co1-inline.xml - 2"""
+
+ import svn_tree
+
+ wc_dir = 'wc-t3'
+
+ # actually do the check out
+ svn_test_main.run_svn ('co', '-d', wc_dir, \
+ '--xml-file', XML_DIR + '/co1-inline.xml', \
+ '-r 1', ANCESTOR_PATH)
+
+
+ exp_tree = svn_tree.build_tree_from_paths(greek_paths)
+ result_tree = svn_tree.build_tree_from_wc(wc_dir)
+
+ if svn_tree.compare_trees(exp_tree, result_tree):
+ return 0
+ else:
+ return 1
+
+#----------------------------------------------------------------------------
+
+def xml_test_4():
+ """verify that the entries files match the wc"""
+
+ import svn_tree, svn_entry
+
+ wc_dir = 'wc-t4'
+
+ # actually do the checkout
+ svn_test_main.run_svn ('co', '-d', wc_dir, \
+ '--xml-file', XML_DIR + '/co1-inline.xml', \
+ '-r 1', ANCESTOR_PATH)
+
+ exp_tree = svn_tree.build_tree_from_wc(wc_dir)
+ result_tree = svn_tree.build_tree_from_entries(wc_dir)
+
+ if svn_tree.compare_trees(exp_tree, result_tree):
+ return 0
+ else:
+ return 1
+
########################################################################
## List all tests here, starting with None:
test_list = [ None,
xml_test_1,
- xml_test_2
+ xml_test_2,
+ xml_test_3,
+ xml_test_4
]
-## And run the main test routine on them:
-svn_test_main.client_test(test_list)
+if __name__ == '__main__':
+ ## And run the main test routine on them:
+ svn_test_main.client_test(test_list)
### End of file.
# local variables:
sam th --- sam_at_uchicago.edu --- http://www.abisource.com/~sam/
OpenPGP Key: CABD33FC --- http://samth.dyndns.org/key
DeCSS: http://samth.dyndns.org/decss
- application/pgp-signature attachment: stored
Received on Sat Oct 21 14:36:29 2006