Change the 'svn status' letter for tree conflicts from 'T' to 'C', following
[svnrdump.git] / svntest / wc.py
blob52fe6278f54199d5cea3a0472f2bea768e9aa196
2 # wc.py: functions for interacting with a Subversion working copy
4 # Subversion is a tool for revision control.
5 # See http://subversion.tigris.org for more information.
7 # ====================================================================
8 # Copyright (c) 2000-2006 CollabNet. All rights reserved.
10 # This software is licensed as described in the file COPYING, which
11 # you should have received as part of this distribution. The terms
12 # are also available at http://subversion.tigris.org/license-1.html.
13 # If newer versions of this license are posted there, you may use a
14 # newer version instead, at your option.
16 ######################################################################
18 import os
19 import types
20 import sys
22 import svntest.tree
25 class State:
26 """Describes an existing or expected state of a working copy.
28 The primary metaphor here is a dictionary of paths mapping to instances
29 of StateItem, which describe each item in a working copy.
31 Note: the paths should be *relative* to the root of the working copy.
32 """
34 def __init__(self, wc_dir, desc):
35 "Create a State using the specified description."
36 assert isinstance(desc, types.DictionaryType)
38 self.wc_dir = wc_dir
39 self.desc = desc # dictionary: path -> StateItem
41 def add(self, more_desc):
42 "Add more state items into the State."
43 assert isinstance(more_desc, types.DictionaryType)
45 self.desc.update(more_desc)
47 def add_state(self, parent, state):
48 "Import state items from a State object, reparent the items to PARENT."
49 assert isinstance(state, State)
51 if parent and parent[-1] != '/':
52 parent += '/'
53 for path, item in state.desc.items():
54 path = parent + path
55 self.desc[path] = item
57 def remove(self, *paths):
58 "Remove a path from the state (the path must exist)."
59 for path in paths:
60 if sys.platform == 'win32':
61 path = path.replace('\\', '/')
62 del self.desc[path]
64 def copy(self, new_root=None):
65 """Make a deep copy of self. If NEW_ROOT is not None, then set the
66 copy's wc_dir NEW_ROOT instead of to self's wc_dir."""
67 desc = { }
68 for path, item in self.desc.items():
69 desc[path] = item.copy()
70 if new_root is None:
71 new_root = self.wc_dir
72 return State(new_root, desc)
74 def tweak(self, *args, **kw):
75 """Tweak the items' values, optional restricting based on a filter.
77 The general form of this method is .tweak(paths..., key=value). If
78 one or more paths are provided, then those items' values are
79 modified. If no paths are given, then all items are modified.
80 """
81 if args:
82 for path in args:
83 try:
84 if sys.platform == 'win32':
85 path = path.replace('\\', '/')
86 path_ref = self.desc[path]
87 except KeyError, e:
88 e.args = ["Path '%s' not present in WC state descriptor" % path]
89 raise
90 apply(path_ref.tweak, (), kw)
91 else:
92 for item in self.desc.values():
93 apply(item.tweak, (), kw)
95 def tweak_some(self, filter, **kw):
96 "Tweak the items for which the filter returns true."
97 for path, item in self.desc.items():
98 if filter(path, item):
99 apply(item.tweak, (), kw)
101 def subtree(self, subtree_path):
102 """Return a State object which is a deep copy of the sub-tree
103 identified by SUBTREE_PATH (which is assumed to contain only one
104 element rooted at the tree of this State object's WC_DIR)."""
105 desc = { }
106 for path, item in self.desc.items():
107 path_elements = path.split("/")
108 if len(path_elements) > 1 and path_elements[0] == subtree_path:
109 desc["/".join(path_elements[1:])] = item.copy()
110 return State(self.wc_dir, desc)
112 def write_to_disk(self, target_dir):
113 """Construct a directory structure on disk, matching our state.
115 WARNING: any StateItem that does not have contents (.contents is None)
116 is assumed to be a directory.
118 if not os.path.exists(target_dir):
119 os.makedirs(target_dir)
121 for path, item in self.desc.items():
122 fullpath = os.path.join(target_dir, path)
123 if item.contents is None:
124 # a directory
125 if not os.path.exists(fullpath):
126 os.makedirs(fullpath)
127 else:
128 # a file
130 # ensure its directory exists
131 dirpath = os.path.dirname(fullpath)
132 if not os.path.exists(dirpath):
133 os.makedirs(dirpath)
135 # write out the file contents now
136 open(fullpath, 'wb').write(item.contents)
138 def old_tree(self):
139 "Return an old-style tree (for compatibility purposes)."
140 nodelist = [ ]
141 for path, item in self.desc.items():
142 atts = { }
143 if item.status is not None:
144 atts['status'] = item.status
145 if item.verb is not None:
146 atts['verb'] = item.verb
147 if item.wc_rev is not None:
148 atts['wc_rev'] = item.wc_rev
149 if item.locked is not None:
150 atts['locked'] = item.locked
151 if item.copied is not None:
152 atts['copied'] = item.copied
153 if item.switched is not None:
154 atts['switched'] = item.switched
155 if item.writelocked is not None:
156 atts['writelocked'] = item.writelocked
157 if item.treeconflict is not None:
158 atts['treeconflict'] = item.treeconflict
159 nodelist.append((os.path.normpath(os.path.join(self.wc_dir, path)),
160 item.contents,
161 item.props,
162 atts))
164 return svntest.tree.build_generic_tree(nodelist)
166 def __str__(self):
167 return str(self.old_tree())
169 class StateItem:
170 """Describes an individual item within a working copy.
172 Note that the location of this item is not specified. An external
173 mechanism, such as the State class, will provide location information
174 for each item.
177 def __init__(self, contents=None, props=None,
178 status=None, verb=None, wc_rev=None,
179 locked=None, copied=None, switched=None, writelocked=None,
180 treeconflict=None):
181 # provide an empty prop dict if it wasn't provided
182 if props is None:
183 props = { }
185 ### keep/make these ints one day?
186 if wc_rev is not None:
187 wc_rev = str(wc_rev)
189 # Any attribute can be None if not relevant, unless otherwise stated.
191 # A string of content (if the node is a file).
192 self.contents = contents
193 # A dictionary mapping prop name to prop value; never None.
194 self.props = props
195 # A two-character string from the first two columns of 'svn status'.
196 self.status = status
197 # The action word such as 'Adding' printed by commands like 'svn update'.
198 self.verb = verb
199 # The base revision number of the node in the WC, as a string.
200 self.wc_rev = wc_rev
201 # For the following attributes, the value is the status character of that
202 # field from 'svn status', except using value None instead of status ' '.
203 self.locked = locked
204 self.copied = copied
205 self.switched = switched
206 self.writelocked = writelocked
207 # Value 'C' or ' ', or None as an expected status meaning 'do not check'.
208 self.treeconflict = treeconflict
210 def copy(self):
211 "Make a deep copy of self."
212 new = StateItem()
213 vars(new).update(vars(self))
214 new.props = self.props.copy()
215 return new
217 def tweak(self, **kw):
218 for name, value in kw.items():
219 ### refine the revision args (for now) to ensure they are strings
220 if value is not None and name == 'wc_rev':
221 value = str(value)
222 setattr(self, name, value)