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 ######################################################################
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.
34 def __init__(self
, wc_dir
, desc
):
35 "Create a State using the specified description."
36 assert isinstance(desc
, types
.DictionaryType
)
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] != '/':
53 for path
, item
in state
.desc
.items():
55 self
.desc
[path
] = item
57 def remove(self
, *paths
):
58 "Remove a path from the state (the path must exist)."
60 if sys
.platform
== 'win32':
61 path
= path
.replace('\\', '/')
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."""
68 for path
, item
in self
.desc
.items():
69 desc
[path
] = item
.copy()
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.
84 if sys
.platform
== 'win32':
85 path
= path
.replace('\\', '/')
86 path_ref
= self
.desc
[path
]
88 e
.args
= ["Path '%s' not present in WC state descriptor" % path
]
90 apply(path_ref
.tweak
, (), kw
)
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)."""
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:
125 if not os
.path
.exists(fullpath
):
126 os
.makedirs(fullpath
)
130 # ensure its directory exists
131 dirpath
= os
.path
.dirname(fullpath
)
132 if not os
.path
.exists(dirpath
):
135 # write out the file contents now
136 open(fullpath
, 'wb').write(item
.contents
)
139 "Return an old-style tree (for compatibility purposes)."
141 for path
, item
in self
.desc
.items():
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
)),
164 return svntest
.tree
.build_generic_tree(nodelist
)
167 return str(self
.old_tree())
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
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,
181 # provide an empty prop dict if it wasn't provided
185 ### keep/make these ints one day?
186 if wc_rev
is not None:
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.
195 # A two-character string from the first two columns of 'svn status'.
197 # The action word such as 'Adding' printed by commands like 'svn update'.
199 # The base revision number of the node in the WC, as a string.
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 ' '.
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
211 "Make a deep copy of self."
213 vars(new
).update(vars(self
))
214 new
.props
= self
.props
.copy()
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':
222 setattr(self
, name
, value
)