3 # objects.py: Objects that keep track of state during a test
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Licensed to the Apache Software Foundation (ASF) under one
10 # or more contributor license agreements. See the NOTICE file
11 # distributed with this work for additional information
12 # regarding copyright ownership. The ASF licenses this file
13 # to you under the Apache License, Version 2.0 (the
14 # "License"); you may not use this file except in compliance
15 # with the License. You may obtain a copy of the License at
17 # http://www.apache.org/licenses/LICENSE-2.0
19 # Unless required by applicable law or agreed to in writing,
20 # software distributed under the License is distributed on an
21 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22 # KIND, either express or implied. See the License for the
23 # specific language governing permissions and limitations
25 ######################################################################
28 import shutil
, sys
, re
, os
, subprocess
32 from svntest
import actions
, main
, tree
, verify
, wc
35 ######################################################################
39 """Convert a path from internal style ('/' separators) to the local style."""
42 return os
.sep
.join(path
.split('/'))
45 def db_dump(repo_path
, table
):
46 """Yield a human-readable representation of the rows of the BDB table
47 TABLE in the repo at REPO_PATH. Yield one line of text at a time.
48 Calls the external program "db_dump" which is supplied with BDB."""
49 table_path
= repo_path
+ "/db/" + table
50 process
= subprocess
.Popen(["db_dump", "-p", table_path
],
51 stdout
=subprocess
.PIPE
, universal_newlines
=True)
52 retcode
= process
.wait()
55 # Strip out the header and footer; copy the rest into FILE.
57 for line
in process
.stdout
.readlines():
58 if line
== "HEADER=END\n":
60 elif line
== "DATA=END\n":
65 def crude_bdb_parse(line
):
66 """Replace BDB ([klen] kval) syntax with (kval) syntax. This is a very
67 crude parser, only just good enough to make a more human-friendly output
68 in common cases. The result is not intended to be unambiguous or machine-
70 lparts
= line
.split('(')
73 rparts
= lpart
.split(')')
76 words
= rpart
.split(' ')
78 # Set new_words to all the words that we want to keep
88 if last_word
is not None:
89 new_words
+= [last_word
]
93 else: # print a word that was previously prefixed by its len
94 if not len(word
) == last_len
+ word
.count('\\') * 2:
95 print "## parsed wrongly: %d %d '%s'" % (last_len
, len(word
), word
)
103 if last_word
is not None:
104 new_words
+= [last_word
]
106 new_rparts
+= [' '.join(new_words
)]
107 new_lparts
+= [')'.join(new_rparts
)]
108 new_line
= '('.join(new_lparts
)
111 def dump_bdb(repo_path
, dump_dir
):
112 """Dump all the known BDB tables in the repository at REPO_PATH into a
113 single text file in DUMP_DIR. Omit any "next-key" records."""
114 dump_file
= dump_dir
+ "/all.bdb"
115 file = open(dump_file
, 'w')
116 for table
in ['revisions', 'transactions', 'changes', 'copies', 'nodes',
117 'node-origins', 'representations', 'checksum-reps', 'strings',
118 'locks', 'lock-tokens', 'miscellaneous', 'uuids']:
119 file.write(table
+ ":\n")
120 for line
in db_dump(repo_path
, table
):
121 if line
== " next-key\n":
123 file.write(crude_bdb_parse(line
))
127 # Dump the svn view of the repository
128 #svnadmin dump repo_path > dump_dir + "/dump.svn"
129 #svnadmin lslocks repo_path > dump_dir + "/locks.svn"
130 #svnadmin lstxns repo_path > dump_dir + "/txns.svn"
133 ######################################################################
134 # Class SvnRepository
137 """An object of class SvnRepository represents a Subversion repository,
138 providing both a client-side view and a server-side view."""
140 def __init__(self
, repo_url
, repo_dir
):
141 self
.repo_url
= repo_url
142 self
.repo_absdir
= os
.path
.abspath(repo_dir
)
144 def dump(self
, output_dir
):
145 """Dump the repository into the directory OUTPUT_DIR"""
146 ldir
= local_path(output_dir
)
148 print "## SvnRepository::dump(rep_dir=" + self
.repo_absdir
+ ")"
150 """Run a BDB dump on the repository"""
151 #subprocess.call(["/home/julianfoad/bin/svn-dump-bdb", self.repo_absdir, ldir])
152 dump_bdb(self
.repo_absdir
, ldir
)
154 """Run 'svnadmin dump' on the repository."""
155 exit_code
, stdout
, stderr
= \
156 actions
.run_and_verify_svnadmin(None, None, None,
157 'dump', self
.repo_absdir
)
158 ldumpfile
= local_path(output_dir
+ "/svnadmin.dump")
159 main
.file_write(ldumpfile
, ''.join(stderr
))
160 main
.file_append(ldumpfile
, ''.join(stdout
))
163 def obliterate_node_rev(self
, path
, rev
):
164 """Obliterate the single node-rev PATH in revision REV."""
165 actions
.run_and_verify_svn(None, None, [],
167 self
.repo_url
+ '/' + path
+ '@' + str(rev
))
170 ######################################################################
174 """An object of class SvnWC represents a WC, and provides methods for
175 operating on it. It keeps track of the state of the WC and of the
176 repository, so that the expected results of common operations are
179 Path arguments to class methods paths are relative to the WC dir and
180 in Subversion canonical form ('/' separators).
183 def __init__(self
, wc_dir
):
184 """Initialize the object to use the existing WC at path WC_DIR.
186 self
.wc_absdir
= os
.path
.abspath(wc_dir
)
187 # 'state' is, at all times, the 'wc.State' representation of the state
188 # of the WC, with paths relative to 'wc_absdir'.
189 #self.state = wc.State('', {})
190 initial_wc_tree
= tree
.build_tree_from_wc(self
.wc_absdir
, load_props
=True)
191 self
.state
= initial_wc_tree
.as_state()
195 # This WC's idea of the repository's head revision.
196 # ### Shouldn't be in this class: should ask the repository instead.
199 print "## new SvnWC:"
203 return "SvnWC(head_rev=" + str(self
.head_rev
) + ", state={" + \
204 str(self
.state
.desc
) + \
207 def svn_mkdir(self
, rpath
):
208 lpath
= local_path(rpath
)
209 actions
.run_and_verify_svn(None, None, [], 'mkdir', lpath
)
212 rpath
: wc
.StateItem(status
='A ')
215 # def propset(self, pname, pvalue, *rpaths):
216 # "Set property 'pname' to value 'pvalue' on each path in 'rpaths'"
217 # local_paths = tuple([local_path(rpath) for rpath in rpaths])
218 # actions.run_and_verify_svn(None, None, [], 'propset', pname, pvalue,
221 def svn_set_props(self
, rpath
, props
):
222 """Change the properties of PATH to be the dictionary {name -> value} PROPS.
224 lpath
= local_path(rpath
)
225 #for prop in path's existing props:
226 # actions.run_and_verify_svn(None, None, [], 'propdel',
229 actions
.run_and_verify_svn(None, None, [], 'propset',
230 prop
, props
[prop
], lpath
)
231 self
.state
.tweak(rpath
, props
=props
)
233 def svn_file_create_add(self
, rpath
, content
=None, props
=None):
234 "Make and add a file with some default content, and keyword expansion."
235 lpath
= local_path(rpath
)
236 ldirname
, filename
= os
.path
.split(lpath
)
239 content
= "This is the file '" + filename
+ "'.\n" + \
240 "Last changed in '$Revision$'.\n"
241 main
.file_write(lpath
, content
)
242 actions
.run_and_verify_svn(None, None, [], 'add', lpath
)
245 rpath
: wc
.StateItem(status
='A ')
250 'svn:keywords': 'Revision'
252 self
.svn_set_props(rpath
, props
)
254 def file_modify(self
, rpath
, content
=None, props
=None):
255 "Make text and property mods to a WC file."
256 lpath
= local_path(rpath
)
257 if content
is not None:
258 #main.file_append(lpath, "An extra line.\n")
259 #actions.run_and_verify_svn(None, None, [], 'propset',
260 # 'newprop', 'v', lpath)
261 main
.file_write(lpath
, content
)
262 self
.state
.tweak(rpath
, content
=content
)
263 if props
is not None:
264 self
.set_props(rpath
, props
)
265 self
.state
.tweak(rpath
, props
=props
)
267 def svn_move(self
, rpath1
, rpath2
, parents
=False):
268 """Move/rename the existing WC item RPATH1 to become RPATH2.
269 RPATH2 must not already exist. If PARENTS is true, any missing parents
270 of RPATH2 will be created."""
271 lpath1
= local_path(rpath1
)
272 lpath2
= local_path(rpath2
)
273 args
= [lpath1
, lpath2
]
275 args
+= ['--parents']
276 actions
.run_and_verify_svn(None, None, [], 'copy', *args
)
278 rpath2
: self
.state
.desc
[rpath1
]
280 self
.state
.remove(rpath1
)
282 def svn_copy(self
, rpath1
, rpath2
, parents
=False, rev
=None):
283 """Copy the existing WC item RPATH1 to become RPATH2.
284 RPATH2 must not already exist. If PARENTS is true, any missing parents
285 of RPATH2 will be created. If REV is not None, copy revision REV of
286 the node identified by WC item RPATH1."""
287 lpath1
= local_path(rpath1
)
288 lpath2
= local_path(rpath2
)
289 args
= [lpath1
, lpath2
]
293 args
+= ['--parents']
294 actions
.run_and_verify_svn(None, None, [], 'copy', *args
)
296 rpath2
: self
.state
.desc
[rpath1
]
299 def svn_delete(self
, rpath
):
300 "Delete a WC path locally."
301 lpath
= local_path(rpath
)
302 actions
.run_and_verify_svn(None, None, [], 'delete', lpath
)
304 def svn_commit(self
, rpath
='', log
=''):
305 "Commit a WC path (recursively). Return the new revision number."
306 lpath
= local_path(rpath
)
307 actions
.run_and_verify_svn(None, verify
.AnyOutput
, [],
308 'commit', '-m', log
, lpath
)
309 actions
.run_and_verify_update(lpath
, None, None, None)
311 print "## head-rev == " + str(self
.head_rev
)
314 # def svn_merge(self, rev_spec, source, target, exp_out=None):
315 # """Merge a single change from path 'source' to path 'target'.
316 # SRC_CHANGE_NUM is either a number (to cherry-pick that specific change)
317 # or a command-line option revision range string such as '-r10:20'."""
318 # lsource = local_path(source)
319 # ltarget = local_path(target)
320 # if isinstance(rev_spec, int):
321 # rev_spec = '-c' + str(rev_spec)
322 # if exp_out is None:
323 # target_re = re.escape(target)
324 # exp_1 = "--- Merging r.* into '" + target_re + ".*':"
325 # exp_2 = "(A |D |[UG] | [UG]|[UG][UG]) " + target_re + ".*"
326 # exp_out = verify.RegexOutput(exp_1 + "|" + exp_2)
327 # actions.run_and_verify_svn(None, exp_out, [],
328 # 'merge', rev_spec, lsource, ltarget)