* subversion/tests/cmdline/svntest/sandbox.py
[svnrdump.git] / svntest / tree.py
blob8299eb747c423f9bee2cdea07b8dbf754738f603
2 # tree.py: tools for comparing directory trees
4 # Subversion is a tool for revision control.
5 # See http://subversion.tigris.org for more information.
7 # ====================================================================
8 # Licensed to the Apache Software Foundation (ASF) under one
9 # or more contributor license agreements. See the NOTICE file
10 # distributed with this work for additional information
11 # regarding copyright ownership. The ASF licenses this file
12 # to you under the Apache License, Version 2.0 (the
13 # "License"); you may not use this file except in compliance
14 # with the License. You may obtain a copy of the License at
16 # http://www.apache.org/licenses/LICENSE-2.0
18 # Unless required by applicable law or agreed to in writing,
19 # software distributed under the License is distributed on an
20 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 # KIND, either express or implied. See the License for the
22 # specific language governing permissions and limitations
23 # under the License.
24 ######################################################################
26 import re
27 import os
28 import sys
29 if sys.version_info[0] >= 3:
30 # Python >=3.0
31 from io import StringIO
32 else:
33 # Python <3.0
34 from StringIO import StringIO
35 from xml.dom.minidom import parseString
36 import base64
38 import svntest
40 # Tree Exceptions.
42 # All tree exceptions should inherit from SVNTreeError
43 class SVNTreeError(svntest.Failure):
44 "Exception raised if you screw up in the tree module."
45 pass
47 class SVNTreeUnequal(SVNTreeError):
48 "Exception raised if two trees are unequal."
49 pass
51 class SVNTypeMismatch(SVNTreeError):
52 "Exception raised if one node is file and other is dir"
53 pass
55 #========================================================================
57 # ===> Overview of our Datastructures <===
59 # The general idea here is that many, many things can be represented by
60 # a tree structure:
62 # - a working copy's structure and contents
63 # - the output of 'svn status'
64 # - the output of 'svn checkout/update'
65 # - the output of 'svn commit'
67 # The idea is that a test function creates a "expected" tree of some
68 # kind, and is then able to compare it to an "actual" tree that comes
69 # from running the Subversion client. This is what makes a test
70 # automated; if an actual and expected tree match exactly, then the test
71 # has passed. (See compare_trees() below.)
73 # The SVNTreeNode class is the fundamental data type used to build tree
74 # structures. The class contains a method for "dropping" a new node
75 # into an ever-growing tree structure. (See also create_from_path()).
77 # We have four parsers in this file for the four use cases listed above:
78 # each parser examines some kind of input and returns a tree of
79 # SVNTreeNode objects. (See build_tree_from_checkout(),
80 # build_tree_from_commit(), build_tree_from_status(), and
81 # build_tree_from_wc()). These trees are the "actual" trees that result
82 # from running the Subversion client.
84 # Also necessary, of course, is a convenient way for a test to create an
85 # "expected" tree. The test *could* manually construct and link a bunch
86 # of SVNTreeNodes, certainly. But instead, all the tests are using the
87 # build_generic_tree() routine instead.
89 # build_generic_tree() takes a specially-formatted list of lists as
90 # input, and returns a tree of SVNTreeNodes. The list of lists has this
91 # structure:
93 # [ ['/full/path/to/item', 'text contents', {prop-hash}, {att-hash}],
94 # [...],
95 # [...],
96 # ... ]
98 # You can see that each item in the list essentially defines an
99 # SVNTreeNode. build_generic_tree() instantiates a SVNTreeNode for each
100 # item, and then drops it into a tree by parsing each item's full path.
102 # So a typical test routine spends most of its time preparing lists of
103 # this format and sending them to build_generic_tree(), rather than
104 # building the "expected" trees directly.
106 # ### Note: in the future, we'd like to remove this extra layer of
107 # ### abstraction. We'd like the SVNTreeNode class to be more
108 # ### directly programmer-friendly, providing a number of accessor
109 # ### routines, so that tests can construct trees directly.
111 # The first three fields of each list-item are self-explanatory. It's
112 # the fourth field, the "attribute" hash, that needs some explanation.
113 # The att-hash is used to place extra information about the node itself,
114 # depending on the parsing context:
116 # - in the 'svn co/up' use-case, each line of output starts with two
117 # characters from the set of (A, D, G, U, C, _) or 'Restored'. The
118 # status code is stored in a attribute named 'status'. In the case
119 # of a restored file, the word 'Restored' is stored in an attribute
120 # named 'verb'.
122 # - in the 'svn ci/im' use-case, each line of output starts with one
123 # of the words (Adding, Deleting, Sending). This verb is stored in
124 # an attribute named 'verb'.
126 # - in the 'svn status' use-case (which is always run with the -v
127 # (--verbose) flag), each line of output contains a working revision
128 # number and a two-letter status code similar to the 'svn co/up'
129 # case. This information is stored in attributes named 'wc_rev'
130 # and 'status'. The repository revision is also printed, but it
131 # is ignored.
133 # - in the working-copy use-case, the att-hash is ignored.
136 # Finally, one last explanation: the file 'actions.py' contain a number
137 # of helper routines named 'run_and_verify_FOO'. These routines take
138 # one or more "expected" trees as input, then run some svn subcommand,
139 # then push the output through an appropriate parser to derive an
140 # "actual" tree. Then it runs compare_trees() and raises an exception
141 # on failure. This is why most tests typically end with a call to
142 # run_and_verify_FOO().
146 #========================================================================
148 # A node in a tree.
150 # If CHILDREN is None, then the node is a file. Otherwise, CHILDREN
151 # is a list of the nodes making up that directory's children.
153 # NAME is simply the name of the file or directory. CONTENTS is a
154 # string that contains the file's contents (if a file), PROPS are
155 # properties attached to files or dirs, and ATTS is a dictionary of
156 # other metadata attached to the node.
158 class SVNTreeNode:
160 def __init__(self, name, children=None, contents=None, props={}, atts={}):
161 self.name = name
162 self.children = children
163 self.contents = contents
164 self.props = props
165 self.atts = atts
166 self.path = name
168 # TODO: Check to make sure contents and children are mutually exclusive
170 def add_child(self, newchild):
171 child_already_exists = 0
172 if self.children is None: # if you're a file,
173 self.children = [] # become an empty dir.
174 else:
175 for a in self.children:
176 if a.name == newchild.name:
177 child_already_exists = 1
178 break
180 if child_already_exists:
181 if newchild.children is None:
182 # this is the 'end' of the chain, so copy any content here.
183 a.contents = newchild.contents
184 a.props = newchild.props
185 a.atts = newchild.atts
186 a.path = os.path.join(self.path, newchild.name)
187 else:
188 # try to add dangling children to your matching node
189 for i in newchild.children:
190 a.add_child(i)
191 else:
192 self.children.append(newchild)
193 newchild.path = os.path.join(self.path, newchild.name)
196 def pprint(self, stream = sys.stdout):
197 "Pretty-print the meta data for this node to STREAM."
198 stream.write(" * Node name: %s\n" % self.name)
199 stream.write(" Path: %s\n" % self.path)
200 mime_type = self.props.get("svn:mime-type")
201 if not mime_type or mime_type.startswith("text/"):
202 if self.children is not None:
203 stream.write(" Contents: N/A (node is a directory)\n")
204 else:
205 stream.write(" Contents: %s\n" % self.contents)
206 else:
207 stream.write(" Contents: %d bytes (binary)\n" % len(self.contents))
208 stream.write(" Properties: %s\n" % self.props)
209 stream.write(" Attributes: %s\n" % self.atts)
210 ### FIXME: I'd like to be able to tell the difference between
211 ### self.children is None (file) and self.children == [] (empty
212 ### directory), but it seems that most places that construct
213 ### SVNTreeNode objects don't even try to do that. --xbc
215 ### See issue #1611 about this problem. -kfogel
216 if self.children is not None:
217 stream.write(" Children: %s\n" % len(self.children))
218 else:
219 stream.write(" Children: None (node is probably a file)\n")
220 stream.flush()
222 def get_printable_path(self):
223 """Remove some occurrences of root_node_name = "__SVN_ROOT_NODE",
224 it is in the way when matching for a subtree, and looks bad."""
225 path = self.path
226 if path.startswith(root_node_name + os.sep):
227 path = path[len(root_node_name + os.sep):]
228 return path
230 def print_script(self, stream = sys.stdout, subtree = "", prepend="\n ",
231 drop_empties = True):
232 """Python-script-print the meta data for this node to STREAM.
233 Print only those nodes whose path string starts with the string SUBTREE,
234 and print only the part of the path string that remains after SUBTREE.
235 PREPEND is a string prepended to each node printout (does the line
236 feed if desired, don't include a comma in PREPEND).
237 If DROP_EMPTIES is true, all dir nodes that have no data set in them
238 (no props, no atts) and that have children (so they are included
239 implicitly anyway) are not printed.
240 Return 1 if this node was printed, 0 otherwise (added up by
241 dump_tree_script())"""
243 # figure out if this node would be obsolete to print.
244 if drop_empties and len(self.props) < 1 and len(self.atts) < 1 and \
245 self.contents is None and self.children is not None:
246 return 0
248 path = self.get_printable_path()
250 # remove the subtree path, skip this node if necessary.
251 if path.startswith(subtree):
252 path = path[len(subtree):]
253 else:
254 return 0
256 if path.startswith(os.sep):
257 path = path[1:]
259 line = prepend
260 line += "%-20s: Item(" % ("'%s'" % path)
261 comma = False
263 mime_type = self.props.get("svn:mime-type")
264 if not mime_type or mime_type.startswith("text/"):
265 if self.contents is not None:
266 # Escape some characters for nicer script and readability.
267 # (This is error output. I guess speed is no consideration here.)
268 line += "contents=\"%s\"" % (self.contents
269 .replace('\n','\\n')
270 .replace('"','\\"')
271 .replace('\r','\\r')
272 .replace('\t','\\t'))
273 comma = True
274 else:
275 line += 'content is binary data'
276 comma = True
278 if self.props:
279 if comma:
280 line += ", "
281 line += "props={"
282 comma = False
284 for name in self.props:
285 if comma:
286 line += ", "
287 line += "'%s':'%s'" % (name, self.props[name])
288 comma = True
290 line += "}"
291 comma = True
293 for name in self.atts:
294 if comma:
295 line += ", "
296 line += "%s='%s'" % (name, self.atts[name])
297 comma = True
299 line += "),"
300 stream.write("%s" % line)
301 stream.flush()
302 return 1
305 def __str__(self):
306 s = StringIO()
307 self.pprint(s)
308 return s.getvalue()
311 def __cmp__(self, other):
312 """Define a simple ordering of two nodes without regard to their full
313 path (i.e. position in the tree). This can be used for sorting the
314 children within a directory."""
315 return cmp(self.name, other.name)
317 def as_state(self, prefix=None):
318 root = self
319 if self.path == root_node_name:
320 assert prefix is None
321 wc_dir = ''
322 while True:
323 if root is not self: # don't prepend ROOT_NODE_NAME
324 wc_dir = os.path.join(wc_dir, root.name)
325 if root.contents or root.props or root.atts:
326 break
327 if not root.children or len(root.children) != 1:
328 break
329 root = root.children[0]
330 state = svntest.wc.State(wc_dir, { })
331 if root.contents or root.props or root.atts:
332 state.add({'': root.as_item()})
333 prefix = wc_dir
334 else:
335 assert prefix is not None
337 path = self.path
338 if path.startswith(root_node_name):
339 path = path[len(root_node_name)+1:]
340 # prefix should only be set on a recursion, which means a child,
341 # which means this path better not be the same as the prefix.
342 assert path != prefix, 'not processing a child of the root'
343 l = len(prefix)
344 if l > 0:
345 assert path[:l] == prefix, \
346 '"%s" is not a prefix of "%s"' % (prefix, path)
347 # return the portion after the separator
348 path = path[l+1:].replace(os.sep, '/')
350 state = svntest.wc.State('', {
351 path: self.as_item()
354 if root.children:
355 for child in root.children:
356 state.add_state('', child.as_state(prefix))
358 return state
360 def as_item(self):
361 return svntest.wc.StateItem(self.contents,
362 self.props,
363 self.atts.get('status'),
364 self.atts.get('verb'),
365 self.atts.get('wc_rev'),
366 self.atts.get('locked'),
367 self.atts.get('copied'),
368 self.atts.get('switched'),
369 self.atts.get('writelocked'),
370 self.atts.get('treeconflict'))
372 def recurse(self, function):
373 results = []
374 results += [ function(self) ]
375 if self.children:
376 for child in self.children:
377 results += child.recurse(function)
378 return results
380 def find_node(self, path):
381 if self.get_printable_path() == path:
382 return self
383 if self.children:
384 for child in self.children:
385 result = child.find_node(path)
386 if result:
387 return result
388 return None
390 # reserved name of the root of the tree
391 root_node_name = "__SVN_ROOT_NODE"
394 # helper func
395 def add_elements_as_path(top_node, element_list):
396 """Add the elements in ELEMENT_LIST as if they were a single path
397 below TOP_NODE."""
399 # The idea of this function is to take a list like so:
400 # ['A', 'B', 'C'] and a top node, say 'Z', and generate a tree
401 # like this:
403 # Z -> A -> B -> C
405 # where 1 -> 2 means 2 is a child of 1.
408 prev_node = top_node
409 for i in element_list:
410 new_node = SVNTreeNode(i, None)
411 prev_node.add_child(new_node)
412 prev_node = new_node
415 def compare_atts(a, b):
416 """Compare two dictionaries of attributes, A (actual) and B (expected).
417 If the attribute 'treeconflict' in B is missing or is 'None', ignore it.
418 Return 0 if the same, 1 otherwise."""
419 a = a.copy()
420 b = b.copy()
421 # Remove any attributes to ignore.
422 for att in ['treeconflict']:
423 if (att not in b) or (b[att] is None):
424 if att in a:
425 del a[att]
426 if att in b:
427 del b[att]
428 if a != b:
429 return 1
430 return 0
432 # Helper for compare_trees
433 def compare_file_nodes(a, b):
434 """Compare two nodes, A (actual) and B (expected). Compare their names,
435 contents, properties and attributes, ignoring children. Return 0 if the
436 same, 1 otherwise."""
437 if a.name != b.name:
438 return 1
439 if a.contents != b.contents:
440 return 1
441 if a.props != b.props:
442 return 1
443 return compare_atts(a.atts, b.atts)
446 # Internal utility used by most build_tree_from_foo() routines.
448 # (Take the output and .add_child() it to a root node.)
450 def create_from_path(path, contents=None, props={}, atts={}):
451 """Create and return a linked list of treenodes, given a PATH
452 representing a single entry into that tree. CONTENTS and PROPS are
453 optional arguments that will be deposited in the tail node."""
455 # get a list of all the names in the path
456 # each of these will be a child of the former
457 if os.sep != "/":
458 path = path.replace(os.sep, "/")
459 elements = path.split("/")
460 if len(elements) == 0:
461 ### we should raise a less generic error here. which?
462 raise SVNTreeError
464 root_node = None
466 # if this is Windows: if the path contains a drive name (X:), make it
467 # the root node.
468 if os.name == 'nt':
469 m = re.match("([a-zA-Z]:)(.+)", elements[0])
470 if m:
471 root_node = SVNTreeNode(m.group(1), None)
472 elements[0] = m.group(2)
473 add_elements_as_path(root_node, elements[0:])
475 if not root_node:
476 root_node = SVNTreeNode(elements[0], None)
477 add_elements_as_path(root_node, elements[1:])
479 # deposit contents in the very last node.
480 node = root_node
481 while True:
482 if node.children is None:
483 node.contents = contents
484 node.props = props
485 node.atts = atts
486 break
487 node = node.children[0]
489 return root_node
492 eol_re = re.compile(r'(\r\n|\r)')
494 # helper for build_tree_from_wc()
495 def get_props(paths):
496 """Return a hash of hashes of props for PATHS, using the svn client. Convert
497 each embedded end-of-line to a single LF character."""
499 # It's not kosher to look inside .svn/ and try to read the internal
500 # property storage format. Instead, we use 'svn proplist'. After
501 # all, this is the only way the user can retrieve them, so we're
502 # respecting the black-box paradigm.
504 files = {}
505 exit_code, output, errput = svntest.main.run_svn(1,
506 "proplist",
507 "--verbose",
508 "--xml",
509 *paths)
511 output = (line for line in output if not line.startswith('DBG:'))
512 dom = parseString(''.join(output))
513 target_nodes = dom.getElementsByTagName('target')
514 for target_node in target_nodes:
515 filename = target_node.attributes['path'].nodeValue
516 file_props = {}
517 for property_node in target_node.getElementsByTagName('property'):
518 name = property_node.attributes['name'].nodeValue
519 if property_node.hasChildNodes():
520 text_node = property_node.firstChild
521 value = text_node.nodeValue
522 else:
523 value = ''
524 try:
525 encoding = property_node.attributes['encoding'].nodeValue
526 if encoding == 'base64':
527 value = base64.b64decode(value)
528 else:
529 raise Exception("Unknown encoding '%s' for file '%s' property '%s'"
530 % (encoding, filename, name,))
531 except KeyError:
532 pass
533 # If the property value contained a CR, or if under Windows an
534 # "svn:*" property contains a newline, then the XML output
535 # contains a CR character XML-encoded as '&#13;'. The XML
536 # parser converts it back into a CR character. So again convert
537 # all end-of-line variants into a single LF:
538 value = eol_re.sub('\n', value)
539 file_props[name] = value
540 files[filename] = file_props
542 dom.unlink()
543 return files
546 ### ridiculous function. callers should do this one line themselves.
547 def get_text(path):
548 "Return a string with the textual contents of a file at PATH."
550 # sanity check
551 if not os.path.isfile(path):
552 return None
554 return open(path, 'r').read()
557 def get_child(node, name):
558 """If SVNTreeNode NODE contains a child named NAME, return child;
559 else, return None. If SVNTreeNode is not a directory, exit completely."""
560 if node.children == None:
561 print("Error: Foolish call to get_child.")
562 sys.exit(1)
563 for n in node.children:
564 if name == n.name:
565 return n
566 return None
569 # Helper for compare_trees
570 def default_singleton_handler(node, description):
571 """Print SVNTreeNode NODE's name, describing it with the string
572 DESCRIPTION, then raise SVNTreeUnequal."""
573 print("Couldn't find node '%s' in %s tree" % (node.name, description))
574 node.pprint()
575 raise SVNTreeUnequal
577 # A test helper function implementing the singleton_handler_a API.
578 def detect_conflict_files(node, extra_files):
579 """NODE has been discovered, an extra file on disk. Verify that it
580 matches one of the regular expressions in the EXTRA_FILES list. If
581 it matches, remove the match from the list. If it doesn't match,
582 raise an exception."""
584 for pattern in extra_files:
585 mo = re.match(pattern, node.name)
586 if mo:
587 extra_files.pop(extra_files.index(pattern)) # delete pattern from list
588 break
589 else:
590 msg = "Encountered unexpected disk path '" + node.name + "'"
591 print(msg)
592 node.pprint()
593 raise SVNTreeUnequal(msg)
595 ###########################################################################
596 ###########################################################################
597 # EXPORTED ROUTINES ARE BELOW
600 # Main tree comparison routine!
602 def compare_trees(label,
603 a, b,
604 singleton_handler_a = None,
605 a_baton = None,
606 singleton_handler_b = None,
607 b_baton = None):
608 """Compare SVNTreeNodes A (actual) and B (expected), expressing
609 differences using FUNC_A and FUNC_B. FUNC_A and FUNC_B are
610 functions of two arguments (a SVNTreeNode and a context baton), and
611 may raise exception SVNTreeUnequal, in which case they use the
612 string LABEL to describe the error (their return value is ignored).
613 LABEL is typically "output", "disk", "status", or some other word
614 that labels the trees being compared.
616 If A and B are both files, then return if their contents,
617 properties, and names are all the same; else raise a SVNTreeUnequal.
618 If A is a file and B is a directory, raise a SVNTreeUnequal; same
619 vice-versa. If both are directories, then for each entry that
620 exists in both, call compare_trees on the two entries; otherwise, if
621 the entry exists only in A, invoke FUNC_A on it, and likewise for
622 B with FUNC_B."""
624 def display_nodes(a, b):
625 'Display two nodes, expected and actual.'
626 print("=============================================================")
627 print("Expected '%s' and actual '%s' in %s tree are different!"
628 % (b.name, a.name, label))
629 print("=============================================================")
630 print("EXPECTED NODE TO BE:")
631 print("=============================================================")
632 b.pprint()
633 print("=============================================================")
634 print("ACTUAL NODE FOUND:")
635 print("=============================================================")
636 a.pprint()
638 # Setup singleton handlers
639 if singleton_handler_a is None:
640 singleton_handler_a = default_singleton_handler
641 a_baton = "expected " + label
642 if singleton_handler_b is None:
643 singleton_handler_b = default_singleton_handler
644 b_baton = "actual " + label
646 try:
647 # A and B are both files.
648 if (a.children is None) and (b.children is None):
649 if compare_file_nodes(a, b):
650 display_nodes(a, b)
651 raise SVNTreeUnequal
652 # One is a file, one is a directory.
653 elif (((a.children is None) and (b.children is not None))
654 or ((a.children is not None) and (b.children is None))):
655 display_nodes(a, b)
656 raise SVNTypeMismatch
657 # They're both directories.
658 else:
659 # First, compare the directories' two hashes.
660 if (a.props != b.props) or compare_atts(a.atts, b.atts):
661 display_nodes(a, b)
662 raise SVNTreeUnequal
664 accounted_for = []
665 # For each child of A, check and see if it's in B. If so, run
666 # compare_trees on the two children and add b's child to
667 # accounted_for. If not, run FUNC_A on the child. Next, for each
668 # child of B, check and see if it's in accounted_for. If it is,
669 # do nothing. If not, run FUNC_B on it.
670 for a_child in a.children:
671 b_child = get_child(b, a_child.name)
672 if b_child:
673 accounted_for.append(b_child)
674 compare_trees(label, a_child, b_child,
675 singleton_handler_a, a_baton,
676 singleton_handler_b, b_baton)
677 else:
678 singleton_handler_a(a_child, a_baton)
679 for b_child in b.children:
680 if b_child not in accounted_for:
681 singleton_handler_b(b_child, b_baton)
682 except SVNTypeMismatch:
683 print('Unequal Types: one Node is a file, the other is a directory')
684 raise SVNTreeUnequal
685 except IndexError:
686 print("Error: unequal number of children")
687 raise SVNTreeUnequal
688 except SVNTreeUnequal:
689 if a.name != root_node_name:
690 print("Unequal at node %s" % a.name)
691 raise
695 # Visually show a tree's structure
697 def dump_tree(n,indent=""):
698 """Print out a nice representation of the structure of the tree in
699 the SVNTreeNode N. Prefix each line with the string INDENT."""
701 # Code partially stolen from Dave Beazley
702 tmp_children = sorted(n.children or [])
704 if n.name == root_node_name:
705 print("%s%s" % (indent, "ROOT"))
706 else:
707 print("%s%s" % (indent, n.name))
709 indent = indent.replace("-", " ")
710 indent = indent.replace("+", " ")
711 for i in range(len(tmp_children)):
712 c = tmp_children[i]
713 if i == len(tmp_children)-1:
714 dump_tree(c,indent + " +-- ")
715 else:
716 dump_tree(c,indent + " |-- ")
719 def dump_tree_script__crawler(n, subtree="", stream=sys.stdout):
720 "Helper for dump_tree_script. See that comment."
721 count = 0
723 # skip printing the root node.
724 if n.name != root_node_name:
725 count += n.print_script(stream, subtree)
727 for child in n.children or []:
728 count += dump_tree_script__crawler(child, subtree, stream)
730 return count
733 def dump_tree_script(n, subtree="", stream=sys.stdout, wc_varname='wc_dir'):
734 """Print out a python script representation of the structure of the tree
735 in the SVNTreeNode N. Print only those nodes whose path string starts
736 with the string SUBTREE, and print only the part of the path string
737 that remains after SUBTREE.
738 The result is printed to STREAM.
739 The WC_VARNAME is inserted in the svntest.wc.State(wc_dir,{}) call
740 that is printed out (this is used by factory.py)."""
742 stream.write("svntest.wc.State(" + wc_varname + ", {")
743 count = dump_tree_script__crawler(n, subtree, stream)
744 if count > 0:
745 stream.write('\n')
746 stream.write("})")
749 ###################################################################
750 ###################################################################
751 # PARSERS that return trees made of SVNTreeNodes....
754 ###################################################################
755 # Build an "expected" static tree from a list of lists
758 # Create a list of lists, of the form:
760 # [ [path, contents, props, atts], ... ]
762 # and run it through this parser. PATH is a string, a path to the
763 # object. CONTENTS is either a string or None, and PROPS and ATTS are
764 # populated dictionaries or {}. Each CONTENTS/PROPS/ATTS will be
765 # attached to the basename-node of the associated PATH.
767 def build_generic_tree(nodelist):
768 "Given a list of lists of a specific format, return a tree."
770 root = SVNTreeNode(root_node_name)
772 for list in nodelist:
773 new_branch = create_from_path(list[0], list[1], list[2], list[3])
774 root.add_child(new_branch)
776 return root
779 ####################################################################
780 # Build trees from different kinds of subcommand output.
783 # Parse co/up output into a tree.
785 # Tree nodes will contain no contents, a 'status' att, and a
786 # 'treeconflict' att.
788 def build_tree_from_checkout(lines, include_skipped=True):
789 "Return a tree derived by parsing the output LINES from 'co' or 'up'."
791 return svntest.wc.State.from_checkout(lines, include_skipped).old_tree()
794 # Parse ci/im output into a tree.
796 # Tree nodes will contain no contents, and only one 'verb' att.
798 def build_tree_from_commit(lines):
799 "Return a tree derived by parsing the output LINES from 'ci' or 'im'."
801 return svntest.wc.State.from_commit(lines).old_tree()
804 # Parse status output into a tree.
806 # Tree nodes will contain no contents, and these atts:
808 # 'status', 'wc_rev',
809 # ... and possibly 'locked', 'copied', 'switched',
810 # 'writelocked' and 'treeconflict',
811 # IFF columns non-empty.
814 def build_tree_from_status(lines):
815 "Return a tree derived by parsing the output LINES from 'st -vuq'."
817 return svntest.wc.State.from_status(lines).old_tree()
820 # Parse merge "skipped" output
822 def build_tree_from_skipped(lines):
824 return svntest.wc.State.from_skipped(lines).old_tree()
827 def build_tree_from_diff_summarize(lines):
828 "Build a tree from output of diff --summarize"
830 return svntest.wc.State.from_summarize(lines).old_tree()
833 ####################################################################
834 # Build trees by looking at the working copy
837 # The reason the 'load_props' flag is off by default is because it
838 # creates a drastic slowdown -- we spawn a new 'svn proplist'
839 # process for every file and dir in the working copy!
842 def build_tree_from_wc(wc_path, load_props=0, ignore_svn=1):
843 """Takes WC_PATH as the path to a working copy. Walks the tree below
844 that path, and creates the tree based on the actual found
845 files. If IGNORE_SVN is true, then exclude SVN admin dirs from the tree.
846 If LOAD_PROPS is true, the props will be added to the tree."""
848 return svntest.wc.State.from_wc(wc_path, load_props, ignore_svn).old_tree()