📄 tree.py
字号:
#!/usr/bin/env python## 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, 2006 CollabNet. 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.#######################################################################import reimport stringimport os.pathimport main # the general svntest routines in this module.from svntest import Failure# Tree Exceptions.# All tree exceptions should inherit from SVNTreeErrorclass SVNTreeError(Failure): "Exception raised if you screw up in the tree module." passclass SVNTreeUnequal(SVNTreeError): "Exception raised if two trees are unequal." passclass SVNTreeIsNotDirectory(SVNTreeError): "Exception raised if get_child is passed a file." passclass SVNTypeMismatch(SVNTreeError): "Exception raised if one node is file and other is dir" pass#========================================================================# ===> Overview of our Datastructures <===# The general idea here is that many, many things can be represented by# a tree structure:# - a working copy's structure and contents# - the output of 'svn status'# - the output of 'svn checkout/update'# - the output of 'svn commit'# The idea is that a test function creates a "expected" tree of some# kind, and is then able to compare it to an "actual" tree that comes# from running the Subversion client. This is what makes a test# automated; if an actual and expected tree match exactly, then the test# has passed. (See compare_trees() below.)# The SVNTreeNode class is the fundamental data type used to build tree# structures. The class contains a method for "dropping" a new node# into an ever-growing tree structure. (See also create_from_path()).# We have four parsers in this file for the four use cases listed above:# each parser examines some kind of input and returns a tree of# SVNTreeNode objects. (See build_tree_from_checkout(),# build_tree_from_commit(), build_tree_from_status(), and# build_tree_from_wc()). These trees are the "actual" trees that result# from running the Subversion client.# Also necessary, of course, is a convenient way for a test to create an# "expected" tree. The test *could* manually construct and link a bunch# of SVNTreeNodes, certainly. But instead, all the tests are using the# build_generic_tree() routine instead.# build_generic_tree() takes a specially-formatted list of lists as# input, and returns a tree of SVNTreeNodes. The list of lists has this# structure:# [ ['/full/path/to/item', 'text contents', {prop-hash}, {att-hash}],# [...],# [...],# ... ]# You can see that each item in the list essentially defines an# SVNTreeNode. build_generic_tree() instantiates a SVNTreeNode for each# item, and then drops it into a tree by parsing each item's full path.# So a typical test routine spends most of its time preparing lists of# this format and sending them to build_generic_tree(), rather than# building the "expected" trees directly.# ### Note: in the future, we'd like to remove this extra layer of# ### abstraction. We'd like the SVNTreeNode class to be more# ### directly programmer-friendly, providing a number of accessor# ### routines, so that tests can construct trees directly.# The first three fields of each list-item are self-explanatory. It's# the fourth field, the "attribute" hash, that needs some explanation.# The att-hash is used to place extra information about the node itself,# depending on the parsing context:# - in the 'svn co/up' use-case, each line of output starts with two# characters from the set of (A, D, G, U, C, _) or 'Restored'. The# status code is stored in a attribute named 'status'. In the case# of a restored file, the word 'Restored' is stored in an attribute# named 'verb'.# - in the 'svn ci/im' use-case, each line of output starts with one# of the words (Adding, Deleting, Sending). This verb is stored in# an attribute named 'verb'.# - in the 'svn status' use-case (which is always run with the -v# (--verbose) flag), each line of output contains a working revision# number and a two-letter status code similar to the 'svn co/up'# case. This information is stored in attributes named 'wc_rev'# and 'status'. The repository revision is also printed, but it# is ignored.# - in the working-copy use-case, the att-hash is ignored.# Finally, one last explanation: the file 'actions.py' contain a number# of helper routines named 'run_and_verify_FOO'. These routines take# one or more "expected" trees as input, then run some svn subcommand,# then push the output through an appropriate parser to derive an# "actual" tree. Then it runs compare_trees() and raises an exception# on failure. This is why most tests typically end with a call to# run_and_verify_FOO().#========================================================================# A node in a tree.## If CHILDREN is None, then the node is a file. Otherwise, CHILDREN# is a list of the nodes making up that directory's children.## NAME is simply the name of the file or directory. CONTENTS is a# string that contains the file's contents (if a file), PROPS are# properties attached to files or dirs, and ATTS is a dictionary of# other metadata attached to the node.class SVNTreeNode: def __init__(self, name, children=None, contents=None, props={}, atts={}): self.name = name self.children = children self.contents = contents self.props = props self.atts = atts self.path = name# TODO: Check to make sure contents and children are mutually exclusive def add_child(self, newchild): if self.children is None: # if you're a file, self.children = [] # become an empty dir. child_already_exists = 0 for a in self.children: if a.name == newchild.name: child_already_exists = 1 break if child_already_exists == 0: self.children.append(newchild) newchild.path = os.path.join (self.path, newchild.name) # If you already have the node, else: if newchild.children is None: # this is the 'end' of the chain, so copy any content here. a.contents = newchild.contents a.props = newchild.props a.atts = newchild.atts a.path = os.path.join (self.path, newchild.name) else: # try to add dangling children to your matching node for i in newchild.children: a.add_child(i) def pprint(self): print " * Node name: ", self.name print " Path: ", self.path print " Contents: ", self.contents print " Properties:", self.props print " Attributes:", self.atts ### FIXME: I'd like to be able to tell the difference between ### self.children is None (file) and self.children == [] (empty ### directory), but it seems that most places that construct ### SVNTreeNode objects don't even try to do that. --xbc ### ### See issue #1611 about this problem. -kfogel if self.children is not None: print " Children: ", len(self.children) else: print " Children: is a file."# reserved name of the root of the treeroot_node_name = "__SVN_ROOT_NODE"# helper funcdef add_elements_as_path(top_node, element_list): """Add the elements in ELEMENT_LIST 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. # prev_node = top_node for i in element_list: new_node = SVNTreeNode(i, None) prev_node.add_child(new_node) prev_node = new_node# Sorting function -- sort 2 nodes by their names.def node_is_greater(a, b): "Sort the names of two nodes." # Interal use only if a.name == b.name: return 0 if a.name > b.name: return 1 else: return -1# Helper for compare_treesdef compare_file_nodes(a, b): """Compare two nodes' names, contents, and properties, ignoring children. Return 0 if the same, 1 otherwise.""" if a.name != b.name: return 1 if a.contents != b.contents: return 1 if a.props != b.props: return 1 if a.atts != b.atts: return 1# Internal utility used by most build_tree_from_foo() routines.## (Take the output and .add_child() it to a root node.)def create_from_path(path, contents=None, props={}, atts={}): """Create and return a linked list of treenodes, given a PATH representing a single entry into that tree. CONTENTS and PROPS are optional arguments that will be deposited in the tail node.""" # get a list of all the names in the path # each of these will be a child of the former if os.sep != "/": path = string.replace(path, os.sep, "/") elements = path.split("/") if len(elements) == 0: ### we should raise a less generic error here. which? raise SVNTreeError root_node = SVNTreeNode(elements[0], None) add_elements_as_path(root_node, elements[1:]) # deposit contents in the very last node. node = root_node while 1: if node.children is None: node.contents = contents node.props = props node.atts = atts break node = node.children[0] return root_node# helper for handle_dir(), which is a helper for build_tree_from_wc()def get_props(path): "Return a hash of props for PATH, using the svn client." # It's not kosher to look inside .svn/ and try to read the internal # property storage format. Instead, we use 'svn proplist'. After # all, this is the only way the user can retrieve them, so we're # respecting the black-box paradigm. props = {} output, errput = main.run_svn(1, "proplist", path, "--verbose") first_value = 0 for line in output: if line.startswith('Properties on '): continue # Not a misprint; "> 0" really is preferable to ">= 0" in this case. if line.find(' : ') > 0: name, value = line.split(' : ') name = string.strip(name) value = string.strip(value) props[name] = value first_value = 1 else: # Multi-line property, so re-use the current name. if first_value: # Undo, as best we can, the strip(value) that was done before # we knew this was a multiline property. props[name] = props[name] + "\n" first_value = 0 props[name] = props[name] + line return props# helper for handle_dir(), which helps build_tree_from_wc()def get_text(path): "Return a string with the textual contents of a file at PATH." # sanity check if not os.path.isfile(path): return None fp = open(path, 'r') contents = fp.read() fp.close() return contents# main recursive helper for build_tree_from_wc()def handle_dir(path, current_parent, load_props, ignore_svn): # 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) != main.get_admin_name()): dirs.append(f) elif os.path.isfile(f): files.append(f) # add each file as a child of CURRENT_PARENT for f in files: fcontents = get_text(f) if load_props: fprops = get_props(f) else: fprops = {} current_parent.add_child(SVNTreeNode(os.path.basename(f), None, fcontents, fprops)) # for each subdir, create a node, walk its tree, add it as a child for d in dirs: if load_props: dprops = get_props(d) else: dprops = {} new_dir_node = SVNTreeNode(os.path.basename(d), None, None, dprops) handle_dir(d, new_dir_node, load_props, ignore_svn) current_parent.add_child(new_dir_node)def get_child(node, name): """If SVNTreeNode NODE contains a child named NAME, return child; else, return None. If SVNTreeNode is not a directory, raise a SVNTreeIsNotDirectory exception"""
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -