You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2013/02/04 21:48:13 UTC
svn commit: r1442344 [34/39] - in /subversion/branches/fsfs-format7: ./
build/ build/ac-macros/ build/generator/ build/generator/templates/
build/win32/ contrib/client-side/emacs/
contrib/server-side/fsfsfixer/fixer/ contrib/server-side/svncutter/ doc/...
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/main.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/main.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/main.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/main.py Mon Feb 4 20:48:05 2013
@@ -157,6 +157,15 @@ atomic_ra_revprop_change_binary = os.pat
wc_lock_tester_binary = os.path.abspath('../libsvn_wc/wc-lock-tester' + _exe)
wc_incomplete_tester_binary = os.path.abspath('../libsvn_wc/wc-incomplete-tester' + _exe)
+######################################################################
+# The location of svnauthz binary, relative to the only scripts that
+# import this file right now (they live in ../).
+# Use --tools to overide these defaults.
+svnauthz_binary = os.path.abspath('../../../tools/server-side/svnauthz' + _exe)
+svnauthz_validate_binary = os.path.abspath(
+ '../../../tools/server-side/svnauthz-validate' + _exe
+)
+
# Location to the pristine repository, will be calculated from test_area_url
# when we know what the user specified for --url.
pristine_greek_repos_url = None
@@ -343,7 +352,7 @@ def filter_dbg(lines):
return included
# Run any binary, logging the command line and return code
-def run_command(command, error_expected, binary_mode=0, *varargs):
+def run_command(command, error_expected, binary_mode=False, *varargs):
"""Run COMMAND with VARARGS. Return exit code as int; stdout, stderr
as lists of lines (including line terminators). See run_command_stdin()
for details. If ERROR_EXPECTED is None, any stderr output will be
@@ -451,17 +460,20 @@ def wait_on_pipe(waiter, binary_mode, st
logger.info("CMD: %s exited with %d" % (command_string, exit_code))
return stdout_lines, stderr_lines, exit_code
-def spawn_process(command, bufsize=-1, binary_mode=0, stdin_lines=None,
+def spawn_process(command, bufsize=-1, binary_mode=False, stdin_lines=None,
*varargs):
"""Run any binary, supplying input text, logging the command line.
+
BUFSIZE dictates the pipe buffer size used in communication with the
- subprocess: 0 means unbuffered, 1 means line buffered, any other
- positive value means use a buffer of (approximately) that size.
- A negative bufsize means to use the system default, which usually
- means fully buffered. The default value for bufsize is 0 (unbuffered).
+ subprocess: quoting from subprocess.Popen(), "0 means unbuffered,
+ 1 means line buffered, any other positive value means use a buffer of
+ (approximately) that size. A negative bufsize means to use the system
+ default, which usually means fully buffered."
+
Normalize Windows line endings of stdout and stderr if not BINARY_MODE.
Return exit code as int; stdout, stderr as lists of lines (including
- line terminators)."""
+ line terminators).
+ """
if stdin_lines and not isinstance(stdin_lines, list):
raise TypeError("stdin_lines should have list type")
@@ -484,7 +496,7 @@ def spawn_process(command, bufsize=-1, b
return exit_code, stdout_lines, stderr_lines
-def run_command_stdin(command, error_expected, bufsize=-1, binary_mode=0,
+def run_command_stdin(command, error_expected, bufsize=-1, binary_mode=False,
stdin_lines=None, *varargs):
"""Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
which should include newline characters) to program via stdin - this
@@ -651,13 +663,6 @@ def _with_auth(args):
else:
return args + ('--username', wc_author )
-def _with_log_message(args):
-
- if '-m' in args or '--message' in args or '-F' in args:
- return args
- else:
- return args + ('--message', 'default log message')
-
# For running subversion and returning the output
def run_svn(error_expected, *varargs):
"""Run svn with VARARGS; return exit code as int; stdout, stderr as
@@ -666,53 +671,63 @@ def run_svn(error_expected, *varargs):
non-zero exit code will raise an exception. If
you're just checking that something does/doesn't come out of
stdout/stderr, you might want to use actions.run_and_verify_svn()."""
- return run_command(svn_binary, error_expected, 0,
+ return run_command(svn_binary, error_expected, False,
*(_with_auth(_with_config_dir(varargs))))
# For running svnadmin. Ignores the output.
def run_svnadmin(*varargs):
"""Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
list of lines (including line terminators)."""
- return run_command(svnadmin_binary, 1, 0, *varargs)
+ return run_command(svnadmin_binary, 1, False, *varargs)
# For running svnlook. Ignores the output.
def run_svnlook(*varargs):
"""Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
list of lines (including line terminators)."""
- return run_command(svnlook_binary, 1, 0, *varargs)
+ return run_command(svnlook_binary, 1, False, *varargs)
def run_svnrdump(stdin_input, *varargs):
"""Run svnrdump with VARARGS, returns exit code as int; stdout, stderr as
list of lines (including line terminators). Use binary mode for output."""
if stdin_input:
- return run_command_stdin(svnrdump_binary, 1, 1, 1, stdin_input,
+ return run_command_stdin(svnrdump_binary, 1, 1, True, stdin_input,
*(_with_auth(_with_config_dir(varargs))))
else:
- return run_command(svnrdump_binary, 1, 1,
+ return run_command(svnrdump_binary, 1, True,
*(_with_auth(_with_config_dir(varargs))))
def run_svnsync(*varargs):
"""Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
list of lines (including line terminators)."""
- return run_command(svnsync_binary, 1, 0, *(_with_config_dir(varargs)))
+ return run_command(svnsync_binary, 1, False, *(_with_config_dir(varargs)))
def run_svnversion(*varargs):
"""Run svnversion with VARARGS, returns exit code as int; stdout, stderr
as list of lines (including line terminators)."""
- return run_command(svnversion_binary, 1, 0, *varargs)
+ return run_command(svnversion_binary, 1, False, *varargs)
def run_svnmucc(*varargs):
"""Run svnmucc with VARARGS, returns exit code as int; stdout, stderr as
list of lines (including line terminators). Use binary mode for output."""
- return run_command(svnmucc_binary, 1, 1,
- *(_with_auth(_with_config_dir(_with_log_message(varargs)))))
+ return run_command(svnmucc_binary, 1, True,
+ *(_with_auth(_with_config_dir(varargs))))
+
+def run_svnauthz(*varargs):
+ """Run svnauthz with VARARGS, returns exit code as int; stdout, stderr
+ as list of lines (including line terminators)."""
+ return run_command(svnauthz_binary, 1, False, *varargs)
+
+def run_svnauthz_validate(*varargs):
+ """Run svnauthz-validate with VARARGS, returns exit code as int; stdout,
+ stderr as list of lines (including line terminators)."""
+ return run_command(svnauthz_validate_binary, 1, False, *varargs)
def run_entriesdump(path):
"""Run the entries-dump helper, returning a dict of Entry objects."""
# use spawn_process rather than run_command to avoid copying all the data
# to stdout in verbose mode.
exit_code, stdout_lines, stderr_lines = spawn_process(entriesdump_binary,
- 0, 0, None, path)
+ 0, False, None, path)
if exit_code or stderr_lines:
### report on this? or continue to just skip it?
return None
@@ -728,16 +743,33 @@ def run_entriesdump_subdirs(path):
# use spawn_process rather than run_command to avoid copying all the data
# to stdout in verbose mode.
exit_code, stdout_lines, stderr_lines = spawn_process(entriesdump_binary,
- 0, 0, None, '--subdirs', path)
+ 0, False, None, '--subdirs', path)
return map(lambda line: line.strip(), filter_dbg(stdout_lines))
+def run_entriesdump_tree(path):
+ """Run the entries-dump helper, returning a dict of a dict of Entry objects."""
+ # use spawn_process rather than run_command to avoid copying all the data
+ # to stdout in verbose mode.
+ exit_code, stdout_lines, stderr_lines = spawn_process(entriesdump_binary,
+ 0, False, None,
+ '--tree-dump', path)
+ if exit_code or stderr_lines:
+ ### report on this? or continue to just skip it?
+ return None
+
+ class Entry(object):
+ pass
+ dirs = { }
+ exec(''.join(filter_dbg(stdout_lines)))
+ return dirs
+
def run_atomic_ra_revprop_change(url, revision, propname, skel, want_error):
"""Run the atomic-ra-revprop-change helper, returning its exit code, stdout,
and stderr. For HTTP, default HTTP library is used."""
# use spawn_process rather than run_command to avoid copying all the data
# to stdout in verbose mode.
#exit_code, stdout_lines, stderr_lines = spawn_process(entriesdump_binary,
- # 0, 0, None, path)
+ # 0, False, None, path)
# This passes HTTP_LIBRARY in addition to our params.
return run_command(atomic_ra_revprop_change_binary, True, False,
@@ -759,7 +791,7 @@ def run_wc_incomplete_tester(wc_dir, rev
def youngest(repos_path):
"run 'svnlook youngest' on REPOS_PATH, returns revision as int"
- exit_code, stdout_lines, stderr_lines = run_command(svnlook_binary, None, 0,
+ exit_code, stdout_lines, stderr_lines = run_command(svnlook_binary, None, False,
'youngest', repos_path)
if exit_code or stderr_lines:
raise Failure("Unexpected failure of 'svnlook youngest':\n%s" % stderr_lines)
@@ -839,7 +871,7 @@ def create_repos(path, minor_version = N
opts += ("--compatible-version=1.%d" % (minor_version),)
if options.fs_type is not None:
opts += ("--fs-type=" + options.fs_type,)
- exit_code, stdout, stderr = run_command(svnadmin_binary, 1, 0, "create",
+ exit_code, stdout, stderr = run_command(svnadmin_binary, 1, False, "create",
path, *opts)
# Skip tests if we can't create the repository.
@@ -1033,6 +1065,19 @@ def write_restrictive_svnserve_conf(repo
fp.write("password-db = passwd\n")
fp.close()
+def write_restrictive_svnserve_conf_with_groups(repo_dir,
+ anon_access="none"):
+ "Create a restrictive configuration with groups stored in a separate file."
+
+ fp = open(get_svnserve_conf_file_path(repo_dir), 'w')
+ fp.write("[general]\nanon-access = %s\nauth-access = write\n"
+ "authz-db = authz\ngroups-db = groups\n" % anon_access)
+ if options.enable_sasl:
+ fp.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
+ else:
+ fp.write("password-db = passwd\n")
+ fp.close()
+
# Warning: because mod_dav_svn uses one shared authz file for all
# repositories, you *cannot* use write_authz_file in any test that
# might be run in parallel.
@@ -1068,6 +1113,18 @@ an appropriate list of mappings.
fp.write("[%s%s]\n%s\n" % (prefix, p, r))
fp.close()
+# See the warning about parallel test execution in write_authz_file
+# method description.
+def write_groups_file(sbox, groups):
+ """Write a groups file to SBOX, appropriate for the RA method used,
+with group contents set to GROUPS."""
+ fp = open(sbox.groups_file, 'w')
+ fp.write("[groups]\n")
+ if groups:
+ for p, r in groups.items():
+ fp.write("%s = %s\n" % (p, r))
+ fp.close()
+
def use_editor(func):
os.environ['SVN_EDITOR'] = svneditor_script
os.environ['SVN_MERGE'] = svneditor_script
@@ -1234,6 +1291,17 @@ def server_enforces_date_syntax():
def server_has_atomic_revprop():
return options.server_minor_version >= 7
+def is_plaintext_password_storage_disabled():
+ try:
+ predicate = re.compile("^WARNING: Plaintext password storage is enabled!")
+ code, out, err = run_svn(False, "--version")
+ for line in out:
+ if predicate.match(line):
+ return False
+ except:
+ return False
+ return True
+
######################################################################
@@ -1292,17 +1360,10 @@ class TestSpawningThread(threading.Threa
if options.http_proxy:
args.append('--http-proxy=' + options.http_proxy)
- result, stdout_lines, stderr_lines = spawn_process(command, 0, 0, None,
+ result, stdout_lines, stderr_lines = spawn_process(command, 0, False, None,
*args)
self.results.append((index, result, stdout_lines, stderr_lines))
- if result != 1:
- sys.stdout.write('.')
- else:
- sys.stdout.write('F')
-
- sys.stdout.flush()
-
class TestRunner:
"""Encapsulate a single test case (predicate), including logic for
runing the test and test list output."""
@@ -1542,9 +1603,6 @@ def _internal_run_tests(test_list, testn
results += t.results
results.sort()
- # terminate the line of dots
- print("")
-
# all tests are finished, find out the result and print the logs.
for (index, result, stdout_lines, stderr_lines) in results:
if stdout_lines:
@@ -1646,6 +1704,8 @@ def _create_parser():
help='Path to SSL server certificate.')
parser.add_option('--http-proxy', action='store',
help='Use the HTTP Proxy at hostname:port.')
+ parser.add_option('--tools-bin', action='store', dest='tools_bin',
+ help='Use the svn tools installed in this path')
# most of the defaults are None, but some are other values, set them here
parser.set_defaults(
@@ -1786,6 +1846,8 @@ def execute_tests(test_list, serial_only
global svndumpfilter_binary
global svnversion_binary
global svnmucc_binary
+ global svnauthz_binary
+ global svnauthz_validate_binary
global options
if test_name:
@@ -1903,6 +1965,11 @@ def execute_tests(test_list, serial_only
svnversion_binary = os.path.join(options.svn_bin, 'svnversion' + _exe)
svnmucc_binary = os.path.join(options.svn_bin, 'svnmucc' + _exe)
+ if options.tools_bin:
+ svnauthz_binary = os.path.join(options.tools_bin, 'svnauthz' + _exe)
+ svnauthz_validate_binary = os.path.join(options.tools_bin,
+ 'svnauthz-validate' + _exe)
+
######################################################################
# Cleanup: if a previous run crashed or interrupted the python
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/sandbox.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/sandbox.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/sandbox.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/sandbox.py Mon Feb 4 20:48:05 2013
@@ -78,12 +78,14 @@ class Sandbox:
tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name)
open(tmp_authz_file, 'w').write("[/]\n* = rw\n")
shutil.move(tmp_authz_file, self.authz_file)
+ self.groups_file = os.path.join(svntest.main.work_dir, "groups")
# For svnserve tests we have a per-repository authz file, and it
# doesn't need to be there in order for things to work, so we don't
# have any default contents.
elif self.repo_url.startswith("svn"):
self.authz_file = os.path.join(self.repo_dir, "conf", "authz")
+ self.groups_file = os.path.join(self.repo_dir, "conf", "groups")
def clone_dependent(self, copy_wc=False):
"""A convenience method for creating a near-duplicate of this
@@ -231,8 +233,8 @@ class Sandbox:
target = self.ospath(target)
if message is None:
message = svntest.main.make_log_msg()
- svntest.main.run_svn(False, 'commit', '-m', message,
- target)
+ svntest.actions.run_and_verify_commit(self.wc_dir, None, None, [],
+ '-m', message, target)
def simple_rm(self, *targets):
"""Schedule TARGETS for deletion.
@@ -318,6 +320,13 @@ class Sandbox:
# '*' is evaluated on Windows
self.simple_propset('svn:special', 'X', target)
+ def simple_add_text(self, text, *targets):
+ """Create files containing TEXT as TARGETS"""
+ assert len(targets) > 0
+ for target in targets:
+ svntest.main.file_write(self.ospath(target), text, mode='wb')
+ self.simple_add(*targets)
+
def simple_copy(self, source, dest):
"""Copy SOURCE to DEST in the WC.
SOURCE and DEST are relpaths relative to the WC."""
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/tree.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/tree.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/tree.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/tree.py Mon Feb 4 20:48:05 2013
@@ -253,6 +253,10 @@ class SVNTreeNode:
# remove the subtree path, skip this node if necessary.
if path.startswith(subtree):
path = path[len(subtree):]
+ elif path + os.sep == subtree:
+ # Many callers set subtree to 'some-path' + os.sep. Don't skip the
+ # root node in that case.
+ path = ''
else:
return 0
@@ -834,10 +838,11 @@ def build_tree_from_commit(lines):
# IFF columns non-empty.
#
-def build_tree_from_status(lines):
+def build_tree_from_status(lines, wc_dir_name=None):
"Return a tree derived by parsing the output LINES from 'st -vuq'."
- return svntest.wc.State.from_status(lines).old_tree()
+ return svntest.wc.State.from_status(lines,
+ wc_dir_name=wc_dir_name).old_tree()
# Parse merge "skipped" output
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/verify.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/verify.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/verify.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/verify.py Mon Feb 4 20:48:05 2013
@@ -215,6 +215,7 @@ class ExpectedOutput:
class AnyOutput(ExpectedOutput):
+ """Matches any non-empty output."""
def __init__(self):
ExpectedOutput.__init__(self, None, False)
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/wc.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/wc.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/wc.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/svntest/wc.py Mon Feb 4 20:48:05 2013
@@ -94,7 +94,13 @@ _re_parse_status = re.compile('^([?!MACD
'((?P<wc_rev>\d+|-|\?) +(\d|-|\?)+ +(\S+) +)?'
'(?P<path>.+)$')
-_re_parse_skipped = re.compile("^Skipped[^']* '(.+)'( --.*)?\n")
+_re_parse_status_ex = re.compile('^ ('
+ '( \> moved (from (?P<moved_from>.+)|to (?P<moved_to>.*)))'
+ '|( \> swapped places with (?P<swapped_with>.+).*)'
+ '|(\> (?P<tc>.+))'
+ ')$')
+
+_re_parse_skipped = re.compile("^(Skipped[^']*) '(.+)'( --.*)?\n")
_re_parse_summarize = re.compile("^([MAD ][M ]) (.+)\n")
@@ -139,10 +145,11 @@ class State:
"Import state items from a State object, reparent the items to PARENT."
assert isinstance(state, State)
- if parent and parent[-1] != '/':
- parent += '/'
for path, item in state.desc.items():
- path = parent + path
+ if path == '':
+ path = parent
+ else:
+ path = parent + '/' + path
self.desc[path] = item
def remove(self, *paths):
@@ -254,8 +261,11 @@ class State:
base = to_relpath(os.path.normpath(self.wc_dir))
+ # TODO: We should probably normalize the paths in moved_from and moved_to.
+
desc = dict([(repos_join(base, path), item)
for path, item in self.desc.items()])
+
return State('', desc)
def compare(self, other):
@@ -327,8 +337,16 @@ class State:
if item.status:
# If this is an unversioned tree-conflict, remove it.
# These are only in their parents' THIS_DIR, they don't have entries.
- if item.status[0] in '!?' and item.treeconflict == 'C':
+ if item.status[0] in '!?' and item.treeconflict == 'C' and \
+ item.entry_status is None:
+ del self.desc[path]
+ # Normal externals are not stored in the parent wc, drop the root
+ # and everything in these working copies
+ elif item.status == 'X ' or item.prev_status == 'X ':
del self.desc[path]
+ for p, i in self.desc.copy().items():
+ if p.startswith(path + '/'):
+ del self.desc[p]
else:
# when reading the entry structures, we don't examine for text or
# property mods, so clear those flags. we also do not examine the
@@ -347,6 +365,9 @@ class State:
if item.entry_status is not None:
item.status = item.entry_status
item.entry_status = None
+ if item.entry_copied is not None:
+ item.copied = item.entry_copied
+ item.entry_copied = None
if item.writelocked:
# we don't contact the repository, so our only information is what
# is in the working copy. 'K' means we have one and it matches the
@@ -362,6 +383,11 @@ class State:
item.writelocked = 'K'
elif item.writelocked == 'O':
item.writelocked = None
+ item.moved_from = None
+ item.moved_to = None
+ if path == '':
+ item.switched = None
+ item.treeconflict = None
def old_tree(self):
"Return an old-style tree (for compatibility purposes)."
@@ -397,7 +423,7 @@ class State:
return not self.__eq__(other)
@classmethod
- def from_status(cls, lines):
+ def from_status(cls, lines, wc_dir_name=None):
"""Create a State object from 'svn status' output."""
def not_space(value):
@@ -406,21 +432,50 @@ class State:
return None
desc = { }
+ last = None
for line in lines:
if line.startswith('DBG:'):
continue
- # Quit when we hit an externals status announcement.
- ### someday we can fix the externals tests to expect the additional
- ### flood of externals status data.
- if line.startswith('Performing'):
- break
-
match = _re_parse_status.search(line)
if not match or match.group(10) == '-':
+
+ ex_match = _re_parse_status_ex.search(line)
+
+ if ex_match:
+ if ex_match.group('moved_from'):
+ path = ex_match.group('moved_from')
+ if wc_dir_name and path.startswith(wc_dir_name + os.path.sep):
+ path = path[len(wc_dir_name) + 1:]
+
+ last.tweak(moved_from = to_relpath(path))
+ elif ex_match.group('moved_to'):
+ path = ex_match.group('moved_to')
+ if wc_dir_name and path.startswith(wc_dir_name + os.path.sep):
+ path = path[len(wc_dir_name) + 1:]
+
+ last.tweak(moved_to = to_relpath(path))
+ elif ex_match.group('swapped_with'):
+ path = ex_match.group('swapped_with')
+ if wc_dir_name and path.startswith(wc_dir_name + os.path.sep):
+ path = path[len(wc_dir_name) + 1:]
+
+ last.tweak(moved_to = to_relpath(path))
+ last.tweak(moved_from = to_relpath(path))
+
+ # Parse TC description?
+
# ignore non-matching lines, or items that only exist on repos
continue
+ prev_status = None
+ prev_treeconflict = None
+
+ path = to_relpath(match.group('path'))
+ if path in desc:
+ prev_status = desc[path].status
+ prev_treeconflict = desc[path].treeconflict
+
item = StateItem(status=match.group(1),
locked=not_space(match.group(2)),
copied=not_space(match.group(3)),
@@ -428,8 +483,11 @@ class State:
writelocked=not_space(match.group(5)),
treeconflict=not_space(match.group(6)),
wc_rev=not_space(match.group('wc_rev')),
+ prev_status=prev_status,
+ prev_treeconflict =prev_treeconflict
)
- desc[to_relpath(match.group('path'))] = item
+ desc[path] = item
+ last = item
return cls('', desc)
@@ -444,7 +502,8 @@ class State:
match = _re_parse_skipped.search(line)
if match:
- desc[to_relpath(match.group(1))] = StateItem()
+ desc[to_relpath(match.group(2))] = StateItem(
+ verb=(match.group(1).strip(':')))
return cls('', desc)
@@ -483,12 +542,38 @@ class State:
treeconflict = match.group(3)
else:
treeconflict = None
- desc[to_relpath(match.group(4))] = StateItem(status=match.group(1),
- treeconflict=treeconflict)
+ path = to_relpath(match.group(4))
+ prev_status = None
+ prev_verb = None
+ prev_treeconflict = None
+
+ if path in desc:
+ prev_status = desc[path].status
+ prev_verb = desc[path].verb
+ prev_treeconflict = desc[path].treeconflict
+
+ desc[path] = StateItem(status=match.group(1),
+ treeconflict=treeconflict,
+ prev_status=prev_status,
+ prev_verb=prev_verb,
+ prev_treeconflict=prev_treeconflict)
else:
match = re_extra.search(line)
if match:
- desc[to_relpath(match.group(2))] = StateItem(verb=match.group(1))
+ path = to_relpath(match.group(2))
+ prev_status = None
+ prev_verb = None
+ prev_treeconflict = None
+
+ if path in desc:
+ prev_status = desc[path].status
+ prev_verb = desc[path].verb
+ prev_treeconflict = desc[path].treeconflict
+
+ desc[path] = StateItem(verb=match.group(1),
+ prev_status=prev_status,
+ prev_verb=prev_verb,
+ prev_treeconflict=prev_treeconflict)
return cls('', desc)
@@ -579,21 +664,14 @@ class State:
desc = { }
dot_svn = svntest.main.get_admin_name()
- for dirpath in svntest.main.run_entriesdump_subdirs(base):
+ dump_data = svntest.main.run_entriesdump_tree(base)
- if base == '.' and dirpath != '.':
- dirpath = '.' + os.path.sep + dirpath
+ if not dump_data:
+ # Probably 'svn status' run on an actual only node
+ # ### Improve!
+ return cls('', desc)
- entries = svntest.main.run_entriesdump(dirpath)
- if entries is None:
- continue
-
- if dirpath == '.':
- parent = ''
- elif dirpath.startswith('.' + os.sep):
- parent = to_relpath(dirpath[2:])
- else:
- parent = to_relpath(dirpath)
+ for parent, entries in sorted(dump_data.items()):
parent_url = entries[''].url
@@ -606,6 +684,9 @@ class State:
# entries that are ABSENT don't show up in status
if entry.absent:
continue
+ # entries that are User Excluded don't show up in status
+ if entry.depth == -1:
+ continue
if name and entry.kind == 2:
# stub subdirectory. leave a "missing" StateItem in here. note
# that we can't put the status as "! " because that gets tweaked
@@ -631,6 +712,9 @@ class State:
if implied_url and implied_url != entry.url:
item.switched = 'S'
+ if entry.file_external:
+ item.switched = 'X'
+
return cls('', desc)
@@ -644,9 +728,10 @@ class StateItem:
def __init__(self, contents=None, props=None,
status=None, verb=None, wc_rev=None,
- entry_rev=None, entry_status=None,
+ entry_rev=None, entry_status=None, entry_copied=None,
locked=None, copied=None, switched=None, writelocked=None,
- treeconflict=None):
+ treeconflict=None, moved_from=None, moved_to=None,
+ prev_status=None, prev_verb=None, prev_treeconflict=None):
# provide an empty prop dict if it wasn't provided
if props is None:
props = { }
@@ -663,22 +748,29 @@ class StateItem:
self.props = props
# A two-character string from the first two columns of 'svn status'.
self.status = status
+ self.prev_status = prev_status
# The action word such as 'Adding' printed by commands like 'svn update'.
self.verb = verb
+ self.prev_verb = prev_verb
# The base revision number of the node in the WC, as a string.
self.wc_rev = wc_rev
# These will be set when we expect the wc_rev/status to differ from those
# found in the entries code.
self.entry_rev = entry_rev
self.entry_status = entry_status
+ self.entry_copied = entry_copied
# For the following attributes, the value is the status character of that
# field from 'svn status', except using value None instead of status ' '.
self.locked = locked
self.copied = copied
self.switched = switched
self.writelocked = writelocked
- # Value 'C' or ' ', or None as an expected status meaning 'do not check'.
+ # Value 'C', 'A', 'D' or ' ', or None as an expected status meaning 'do not check'.
self.treeconflict = treeconflict
+ self.prev_treeconflict = prev_treeconflict
+ # Relative paths to the move locations
+ self.moved_from = moved_from
+ self.moved_to = moved_to
def copy(self):
"Make a deep copy of self."
@@ -716,8 +808,12 @@ class StateItem:
atts = { }
if self.status is not None:
atts['status'] = self.status
+ if self.prev_status is not None:
+ atts['prev_status'] = self.prev_status
if self.verb is not None:
atts['verb'] = self.verb
+ if self.prev_verb is not None:
+ atts['prev_verb'] = self.prev_verb
if self.wc_rev is not None:
atts['wc_rev'] = self.wc_rev
if self.locked is not None:
@@ -730,6 +826,12 @@ class StateItem:
atts['writelocked'] = self.writelocked
if self.treeconflict is not None:
atts['treeconflict'] = self.treeconflict
+ if self.prev_treeconflict is not None:
+ atts['prev_treeconflict'] = self.prev_treeconflict
+ if self.moved_from is not None:
+ atts['moved_from'] = self.moved_from
+ if self.moved_to is not None:
+ atts['moved_to'] = self.moved_to
return (os.path.normpath(path), self.contents, self.props, atts)
@@ -827,9 +929,12 @@ def repos_join(base, path):
"""Join two repos paths. This generally works for URLs too."""
if base == '':
return path
- if path == '':
+ elif path == '':
return base
- return base + '/' + path
+ elif base[len(base)-1:] == '/':
+ return base + path
+ else:
+ return base + '/' + path
def svn_uri_quote(url):
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/switch_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/switch_tests.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/switch_tests.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/switch_tests.py Mon Feb 4 20:48:05 2013
@@ -891,7 +891,7 @@ def obstructed_switch(sbox):
# svn info A/B/E/alpha
expected_stdout = verify.RegexOutput(
- ".*local unversioned, incoming add upon switch",
+ ".*local file unversioned, incoming file add upon switch",
match_all=False)
actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'info',
A_B_E_alpha)
@@ -1285,7 +1285,7 @@ def forced_switch_failures(sbox):
# svn info A/B/F/pi
expected_stdout = verify.ExpectedOutput(
- 'Tree conflict: local unversioned, incoming add upon switch\n',
+ 'Tree conflict: local file unversioned, incoming file add upon switch\n',
match_all=False)
actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'info',
@@ -1463,15 +1463,15 @@ def switch_with_obstructing_local_adds(s
expected_status.add({
'A/B/F/gamma' : Item(status='R ', treeconflict='C', wc_rev='1'),
'A/B/F/G' : Item(status='R ', treeconflict='C', wc_rev='1'),
- 'A/B/F/G/pi' : Item(status='A ', wc_rev='-'),
- 'A/B/F/G/tau' : Item(status='A ', wc_rev='-'),
- 'A/B/F/G/upsilon' : Item(status='A ', wc_rev='-'),
+ 'A/B/F/G/pi' : Item(status='A ', wc_rev='-', entry_status='R ', entry_rev='1'),
+ 'A/B/F/G/tau' : Item(status='A ', wc_rev='-', entry_status='R ', entry_rev='1'),
+ 'A/B/F/G/upsilon' : Item(status='A ', wc_rev='-', entry_rev='0'),
'A/B/F/G/rho' : Item(status='D ', wc_rev='1'),
'A/B/F/H' : Item(status=' ', wc_rev='1'),
'A/B/F/H/chi' : Item(status=' ', wc_rev='1'),
'A/B/F/H/omega' : Item(status=' ', wc_rev='1'),
'A/B/F/H/psi' : Item(status=' ', wc_rev='1'),
- 'A/B/F/I' : Item(status='A ', wc_rev='-'),
+ 'A/B/F/I' : Item(status='A ', wc_rev='-', entry_rev='0'),
})
# "Extra" files that we expect to result from the conflicts.
@@ -2213,7 +2213,7 @@ def switch_to_root(sbox):
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau')
expected_status.add_state('A/D/G',
- svntest.actions.get_virginal_state(wc_dir, 1))
+ svntest.actions.get_virginal_state(wc_dir + '/A/D/G', 1))
expected_status.tweak('A/D/G', switched = 'S')
svntest.actions.run_and_verify_switch(wc_dir, ADG_path, sbox.repo_url,
expected_output,
@@ -2328,10 +2328,9 @@ def tree_conflicts_on_switch_1_1(sbox):
})
expected_disk = disk_empty_dirs.copy()
- if svntest.main.wc_is_singledb(sbox.wc_dir):
- expected_disk.remove('D/D1', 'DF/D1', 'DD/D1', 'DD/D1/D2',
- 'DDF/D1', 'DDF/D1/D2',
- 'DDD/D1', 'DDD/D1/D2', 'DDD/D1/D2/D3')
+ expected_disk.remove('D/D1', 'DF/D1', 'DD/D1', 'DD/D1/D2',
+ 'DDF/D1', 'DDF/D1/D2',
+ 'DDD/D1', 'DDD/D1/D2', 'DDD/D1/D2/D3')
# The files delta, epsilon, and zeta are incoming additions, but since
# they are all within locally deleted trees they should also be schedule
@@ -2350,37 +2349,37 @@ def tree_conflicts_on_switch_1_1(sbox):
expected_info = {
'F/alpha' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local file delete, incoming file edit upon switch'
+ ' Source left: .file.*/F/alpha@2'
+ ' Source right: .file.*/F/alpha@3$',
},
'DF/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DF/D1@2'
+ ' Source right: .dir.*/DF/D1@3$',
},
'DDF/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DDF/D1@2'
+ ' Source right: .dir.*/DDF/D1@3$',
},
'D/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/D/D1@2'
+ ' Source right: .dir.*/D/D1@3$',
},
'DD/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DD/D1@2'
+ ' Source right: .dir.*/DD/D1@3$',
},
'DDD/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DDD/D1@2'
+ ' Source right: .dir.*/DDD/D1@3$',
},
@@ -2441,45 +2440,44 @@ def tree_conflicts_on_switch_1_2(sbox):
expected_disk.remove('D/D1',
'DD/D1/D2',
'DDD/D1/D2/D3')
- if svntest.main.wc_is_singledb(sbox.wc_dir):
- expected_disk.remove('DF/D1', 'DD/D1',
- 'DDF/D1', 'DDF/D1/D2',
- 'DDD/D1', 'DDD/D1/D2')
+ expected_disk.remove('DF/D1', 'DD/D1',
+ 'DDF/D1', 'DDF/D1/D2',
+ 'DDD/D1', 'DDD/D1/D2')
expected_info = {
'F/alpha' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local file delete, incoming file delete upon switch'
+ ' Source left: .file.*/F/alpha@2'
+ ' Source right: .none.*(/F/alpha@3)?$',
},
'DF/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DF/D1@2'
+ ' Source right: .dir.*/DF/D1@3$',
},
'DDF/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DDF/D1@2'
+ ' Source right: .dir.*/DDF/D1@3$',
},
'D/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/D/D1@2'
+ ' Source right: .none.*(/D/D1@3)?$',
},
'DD/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DD/D1@2'
+ ' Source right: .dir.*/DD/D1@3$',
},
'DDD/D1' : {
'Tree conflict' :
- '^local delete, incoming edit upon switch'
+ '^local dir delete, incoming dir edit upon switch'
+ ' Source left: .dir.*/DDD/D1@2'
+ ' Source right: .dir.*/DDD/D1@3$',
},
@@ -2532,37 +2530,37 @@ def tree_conflicts_on_switch_2_1(sbox):
expected_info = {
'F/alpha' : {
'Tree conflict' :
- '^local edit, incoming delete upon switch'
+ '^local file edit, incoming file delete upon switch'
+ ' Source left: .file.*/F/alpha@2'
+ ' Source right: .none.*(/F/alpha@3)?$',
},
'DF/D1' : {
'Tree conflict' :
- '^local edit, incoming delete upon switch'
+ '^local dir edit, incoming dir delete upon switch'
+ ' Source left: .dir.*/DF/D1@2'
+ ' Source right: .none.*(/DF/D1@3)?$',
},
'DDF/D1' : {
'Tree conflict' :
- '^local edit, incoming delete upon switch'
+ '^local dir edit, incoming dir delete upon switch'
+ ' Source left: .dir.*/DDF/D1@2'
+ ' Source right: .none.*(/DDF/D1@3)?$',
},
'D/D1' : {
'Tree conflict' :
- '^local edit, incoming delete upon switch'
+ '^local dir edit, incoming dir delete upon switch'
+ ' Source left: .dir.*/D/D1@2'
+ ' Source right: .none.*(/D/D1@3)?$',
},
'DD/D1' : {
'Tree conflict' :
- '^local edit, incoming delete upon switch'
+ '^local dir edit, incoming dir delete upon switch'
+ ' Source left: .dir.*/DD/D1@2'
+ ' Source right: .none.*(/DD/D1@3)?$',
},
'DDD/D1' : {
'Tree conflict' :
- '^local edit, incoming delete upon switch'
+ '^local dir edit, incoming dir delete upon switch'
+ ' Source left: .dir.*/DDD/D1@2'
+ ' Source right: .none.*(/DDD/D1@3)?$',
},
@@ -2631,37 +2629,37 @@ def tree_conflicts_on_switch_2_2(sbox):
expected_info = {
'F/alpha' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local file delete, incoming file delete upon switch'
+ ' Source left: .file.*/F/alpha@2'
+ ' Source right: .none.*(/F/alpha@3)?$',
},
'DF/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DF/D1@2'
+ ' Source right: .none.*(/DF/D1@3)?$',
},
'DDF/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DDF/D1@2'
+ ' Source right: .none.*(/DDF/D1@3)?$',
},
'D/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/D/D1@2'
+ ' Source right: .none.*(/D/D1@3)?$',
},
'DD/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DD/D1@2'
+ ' Source right: .none.*(/DD/D1@3)?$',
},
'DDD/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DDD/D1@2'
+ ' Source right: .none.*(/DDD/D1@3)?$',
},
@@ -2720,37 +2718,37 @@ def tree_conflicts_on_switch_3(sbox):
expected_info = {
'F/alpha' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local file delete, incoming file delete upon switch'
+ ' Source left: .file.*/F/alpha@2'
+ ' Source right: .none.*(/F/alpha@3)?$',
},
'DF/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DF/D1@2'
+ ' Source right: .none.*(/DF/D1@3)?$',
},
'DDF/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DDF/D1@2'
+ ' Source right: .none.*(/DDF/D1@3)?$',
},
'D/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/D/D1@2'
+ ' Source right: .none.*(/D/D1@3)?$',
},
'DD/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DD/D1@2'
+ ' Source right: .none.*(/DD/D1@3)?$',
},
'DDD/D1' : {
'Tree conflict' :
- '^local delete, incoming delete upon switch'
+ '^local dir delete, incoming dir delete upon switch'
+ ' Source left: .dir.*/DDD/D1@2'
+ ' Source right: .none.*(/DDD/D1@3)?$',
},
@@ -2785,7 +2783,7 @@ def copy_with_switched_subdir(sbox):
'--ignore-ancestry', E_url, G)
state.tweak('A/D/G', switched='S')
- state.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau');
+ state.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau')
state.add({
'A/D/G/alpha' : Item(status=' ', wc_rev=1),
'A/D/G/beta' : Item(status=' ', wc_rev=1),
@@ -2908,6 +2906,33 @@ def different_node_kind(sbox):
switch_to_file(sbox, 'iota', 'A/C')
switch_to_file(sbox, 'A/D/gamma', 'A/D/G')
+@Issue(3332, 3333)
+def switch_to_spaces(sbox):
+ "switch to a directory with spaces in its name"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+ repo_url = sbox.repo_url
+
+ # Paths are normalized in the command processing, so %20 is equivalent to ' '
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'cp', repo_url + '/A',
+ repo_url + '/A%20with space',
+ '-m', '')
+
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'mv', repo_url + '/A%20with space',
+ repo_url + '/A with%20more spaces',
+ '-m', '')
+
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
+ expected_status.tweak('A', switched='S')
+ expected_status.tweak('', 'iota', wc_rev=1)
+
+ svntest.actions.run_and_verify_switch(sbox.wc_dir, sbox.ospath('A'),
+ repo_url + '/A%20with more%20spaces',
+ None, None, expected_status)
+
########################################################################
# Run the tests
@@ -2948,6 +2973,7 @@ test_list = [ None,
copy_with_switched_subdir,
up_to_old_rev_with_subtree_switched_to_root,
different_node_kind,
+ switch_to_spaces,
]
if __name__ == '__main__':
Modified: subversion/branches/fsfs-format7/subversion/tests/cmdline/tree_conflict_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/tests/cmdline/tree_conflict_tests.py?rev=1442344&r1=1442343&r2=1442344&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/tests/cmdline/tree_conflict_tests.py (original)
+++ subversion/branches/fsfs-format7/subversion/tests/cmdline/tree_conflict_tests.py Mon Feb 4 20:48:05 2013
@@ -1223,7 +1223,7 @@ def actual_only_node_behaviour(sbox):
# info
expected_info = {
- 'Tree conflict': 'local missing, incoming edit upon merge.*',
+ 'Tree conflict': 'local file missing, incoming file edit upon merge.*',
'Name': 'foo',
'Schedule': 'normal',
'Node Kind': 'none',