diff options
Diffstat (limited to 'subversion/tests')
73 files changed, 3173 insertions, 419 deletions
diff --git a/subversion/tests/afl/afl-svndiff-testcase/test1 b/subversion/tests/afl/afl-svndiff-testcase/test1 Binary files differnew file mode 100644 index 0000000..987db36 --- /dev/null +++ b/subversion/tests/afl/afl-svndiff-testcase/test1 diff --git a/subversion/tests/afl/afl-svndiff-testcase/test2 b/subversion/tests/afl/afl-svndiff-testcase/test2 Binary files differnew file mode 100644 index 0000000..984cbf5 --- /dev/null +++ b/subversion/tests/afl/afl-svndiff-testcase/test2 diff --git a/subversion/tests/afl/afl-svndiff-testcase/test3 b/subversion/tests/afl/afl-svndiff-testcase/test3 Binary files differnew file mode 100644 index 0000000..102ee8e --- /dev/null +++ b/subversion/tests/afl/afl-svndiff-testcase/test3 diff --git a/subversion/tests/afl/afl-svndiff.c b/subversion/tests/afl/afl-svndiff.c new file mode 100644 index 0000000..4ff00bf --- /dev/null +++ b/subversion/tests/afl/afl-svndiff.c @@ -0,0 +1,85 @@ +/* + * afl-svndiff.c an American Fuzz Lop test + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + */ + +/* The input data can either be a file on disk or provided via stdin: + + afl-svndiff some-file + afl-svndiff < some-file + + In practice the file simply contains random binary data. The data + are interpreted as an svndiff as sent by a client to the server. */ + +#include "svn_delta.h" +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_io.h" + +#include <stdlib.h> + +static svn_error_t * +txdelta_window_handler(svn_txdelta_window_t *window, + void *baton) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +parse(const char *filename, apr_pool_t *pool) +{ + svn_stringbuf_t *buf; + svn_stream_t *svndiff; + svn_stream_t *stream; + + SVN_ERR(svn_stringbuf_from_file2(&buf, filename, pool)); + svndiff = svn_stream_from_stringbuf(buf, pool); + + stream = svn_txdelta_parse_svndiff(txdelta_window_handler, NULL, TRUE, pool); + SVN_ERR(svn_stream_copy3(svndiff, stream, NULL, NULL, pool)); + + return SVN_NO_ERROR; +} + +int main(int argc, char **argv) +{ + apr_pool_t *pool; + int exit_code = EXIT_SUCCESS; + svn_error_t *err; + const char *filename; + + if (argc == 2) + filename = argv[1]; + else + filename = "-"; + + if (svn_cmdline_init("afl-svndiff", stderr) != EXIT_SUCCESS) + return EXIT_FAILURE; + pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); + + err = parse(filename, pool); + if (err) + exit_code = EXIT_FAILURE; + svn_error_clear(err); + svn_pool_destroy(pool); + return exit_code; +} diff --git a/subversion/tests/afl/afl-x509-testcase/test2 b/subversion/tests/afl/afl-x509-testcase/test2 Binary files differnew file mode 100644 index 0000000..716ef59 --- /dev/null +++ b/subversion/tests/afl/afl-x509-testcase/test2 diff --git a/subversion/tests/afl/afl-x509-testcase/test3 b/subversion/tests/afl/afl-x509-testcase/test3 Binary files differnew file mode 100644 index 0000000..68d6e7e --- /dev/null +++ b/subversion/tests/afl/afl-x509-testcase/test3 diff --git a/subversion/tests/cmdline/authz_tests.py b/subversion/tests/cmdline/authz_tests.py index 8878418..e818c65 100755 --- a/subversion/tests/cmdline/authz_tests.py +++ b/subversion/tests/cmdline/authz_tests.py @@ -168,7 +168,7 @@ def authz_read_access(sbox): fws_url = B_url + '/folder with spaces' fws_empty_folder_url = fws_url + '/empty folder' - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" @@ -280,7 +280,7 @@ def authz_write_access(sbox): write_restrictive_svnserve_conf(sbox.repo_dir) - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E220004: Access denied.*" @@ -367,7 +367,7 @@ def authz_checkout_test(sbox): # 1st part: disable all read access, checkout should fail # write an authz file with *= on / - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" @@ -502,7 +502,7 @@ def authz_log_and_tracing_test(sbox): write_restrictive_svnserve_conf(sbox.repo_dir) # write an authz file with *=rw on / - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" @@ -533,7 +533,7 @@ def authz_log_and_tracing_test(sbox): # now disable read access on the first version of rho, keep the copy in # /A/D readable. - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" @@ -551,7 +551,7 @@ def authz_log_and_tracing_test(sbox): 'log', '-r', '2', '--limit', '1', wc_dir) - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err2 = expected_err else: expected_err2 = ".*svn: E220001: ((Unreadable path encountered; " \ @@ -593,7 +593,7 @@ def authz_log_and_tracing_test(sbox): svntest.actions.run_and_verify_svn(None, expected_err2, 'cat', '-r', '2', D_url+'/rho') - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err2 = expected_err else: expected_err2 = ".*svn: E220001: Unreadable path encountered; access denied.*" @@ -624,7 +624,7 @@ def authz_aliases(sbox): write_restrictive_svnserve_conf(sbox.repo_dir) - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" @@ -669,9 +669,9 @@ def authz_validate(sbox): write_authz_file(sbox, { "/" : "* = r", "/A/B" : "@undefined_group = rw" }) - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" - elif sbox.repo_url.startswith("svn"): + elif svntest.main.is_ra_type_svn(): expected_err = ".*Invalid authz configuration" else: expected_err = ".*@undefined_group.*" @@ -688,9 +688,9 @@ devs1 = @admins, dev1 devs2 = @admins, dev2 devs = @devs1, dev3, dev4""" }) - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" - elif sbox.repo_url.startswith("svn"): + elif svntest.main.is_ra_type_svn(): expected_err = ".*Invalid authz configuration" else: expected_err = ".*Circular dependency.*" @@ -726,7 +726,7 @@ def authz_locking(sbox): write_authz_file(sbox, {"/": "", "/A": "jrandom = rw"}) write_restrictive_svnserve_conf(sbox.repo_dir) - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: warning: W170001: Authorization failed.*" @@ -781,7 +781,7 @@ def authz_locking(sbox): svntest.actions.run_and_verify_info([{'Lock Token' : None}], sbox.ospath('A/mu')) - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: warning: W160039: .*([Aa]uth.*perf|[Ff]orbidden).*" else: expected_err = ".*svn: warning: W170001: Authorization failed.*" @@ -863,7 +863,7 @@ def authz_svnserve_anon_access_read(sbox): # is not really a branch (it's the same URL), but we only care about # authz here, not the semantics of the merge. (Merges had been # failing in authz, for the reasons summarized in - # http://subversion.tigris.org/issues/show_bug.cgi?id=2712#desc13.) + # https://issues.apache.org/jira/browse/SVN-2712#desc13.) svntest.actions.run_and_verify_svn(None, [], 'merge', '-c', '2', B_url, B_path) @@ -981,7 +981,7 @@ def multiple_matches(sbox): sbox.build(create_wc = False) root_url = sbox.repo_url write_restrictive_svnserve_conf(sbox.repo_dir) - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_err = ".*svn: E175013: .*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" @@ -1137,7 +1137,7 @@ def case_sensitive_authz(sbox): # error messages expected_error_for_commit = ".*Commit failed.*" - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_error_for_cat = ".*[Ff]orbidden.*" else: expected_error_for_cat = ".*svn: E170001: Authorization failed.*" diff --git a/subversion/tests/cmdline/basic_tests.py b/subversion/tests/cmdline/basic_tests.py index 4b0b8d5..581a23b 100755 --- a/subversion/tests/cmdline/basic_tests.py +++ b/subversion/tests/cmdline/basic_tests.py @@ -3024,16 +3024,19 @@ def peg_rev_on_non_existent_wc_path(sbox): # setup some history sbox.simple_move('A', 'A2') sbox.simple_move('A2/mu', 'A2/mu2') - open(sbox.ospath('A2/mu2'), 'w').write('r2\n') + with open(sbox.ospath('A2/mu2'), 'w') as f: + f.write('r2\n') sbox.simple_commit(message='r2') # sbox.simple_move('A2/mu2', 'A2/mu3') sbox.simple_move('A2', 'A3') - open(sbox.ospath('A3/mu3'), 'w').write('r3\n') + with open(sbox.ospath('A3/mu3'), 'w') as f: + f.write('r3\n') sbox.simple_commit(message='r3') # sbox.simple_move('A3/mu3', 'A3/mu4') - open(sbox.ospath('A3/mu4'), 'w').write('r4\n') + with open(sbox.ospath('A3/mu4'), 'w') as f: + f.write('r4\n') sbox.simple_move('A3', 'A4') sbox.simple_commit(message='r4') @@ -3121,24 +3124,27 @@ def filtered_ls(sbox): sbox.build(read_only=True) path = sbox.repo_url + "/A/D" - # check plain info - expected = [ "H/omega\n", - "gamma\n" ] + # with and without externals, because without externals on a 1.10+ server + # a server-side code path is used + for extra_opts in [ [], ['--include-externals'] ]: - exit_code, output, error = svntest.actions.run_and_verify_svn( - expected, [], 'ls', path, '--depth=infinity', '--search=*a') + expected = [ "H/omega\n", + "gamma\n" ] - # check case-insensitivity - exit_code, output, error = svntest.actions.run_and_verify_svn( - expected, [], 'ls', path, '--depth=infinity', '--search=*A') + exit_code, output, error = svntest.actions.run_and_verify_svn( + expected, [], 'ls', path, '--depth=infinity', '--search=*a', *extra_opts) - expected = [ "H/\n" ] - exit_code, output, error = svntest.actions.run_and_verify_svn( - expected, [], 'ls', path, '--depth=infinity', '--search=h') + # check case-insensitivity + exit_code, output, error = svntest.actions.run_and_verify_svn( + expected, [], 'ls', path, '--depth=infinity', '--search=*A', *extra_opts) - # we don't match full paths - exit_code, output, error = svntest.actions.run_and_verify_svn( - [], [], 'ls', path, '--depth=infinity', '--search=*/*') + expected = [ "H/\n" ] + exit_code, output, error = svntest.actions.run_and_verify_svn( + expected, [], 'ls', path, '--depth=infinity', '--search=h', *extra_opts) + + # we don't match full paths + exit_code, output, error = svntest.actions.run_and_verify_svn( + [], [], 'ls', path, '--depth=infinity', '--search=*/*', *extra_opts) @Issue(4700) @XFail(svntest.main.is_fs_type_fsx) @@ -3197,6 +3203,36 @@ def null_prop_update_last_changed_revision(sbox): 'info', sbox.path('iota'), '--show-item', 'last-changed-revision') +@Skip(svntest.main.is_os_windows) +def filtered_ls_top_level_path(sbox): + "filtered 'svn ls' top level path" + + sbox.build(read_only=True, create_wc=False) + d_path = sbox.repo_url + "/A/B" + f_path = sbox.repo_url + "/A/B/lambda" + + d_expected = svntest.verify.RegexListOutput([ + r".* \./", # expect '*B*' to match its name which is 'B' + r".* E/beta", + r".* lambda" ]) + f_expected = [ "lambda\n" ] + + # with and without externals, because without externals on a 1.10+ server + # a server-side code path is used + for extra_opts in [ [], ['--include-externals'] ]: + + exit_code, output, error = svntest.actions.run_and_verify_svn( + d_expected, [], 'ls', '-v', d_path, '-R', '--search=*B*', *extra_opts) + + exit_code, output, error = svntest.actions.run_and_verify_svn( + f_expected, [], 'ls', f_path, '--search=lambda', *extra_opts) + + # we don't match full paths, even for the top level path + exit_code, output, error = svntest.actions.run_and_verify_svn( + [], [], 'ls', '-v', d_path, '-R', '--search=*/*', *extra_opts) + exit_code, output, error = svntest.actions.run_and_verify_svn( + [], [], 'ls', f_path, '--search=*/*', *extra_opts) + ######################################################################## # Run the tests @@ -3271,6 +3307,7 @@ test_list = [ None, filtered_ls, null_update_last_changed_revision, null_prop_update_last_changed_revision, + filtered_ls_top_level_path, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/blame_tests.py b/subversion/tests/cmdline/blame_tests.py index f824d25..8620dd4 100755 --- a/subversion/tests/cmdline/blame_tests.py +++ b/subversion/tests/cmdline/blame_tests.py @@ -958,7 +958,8 @@ def blame_youngest_to_oldest(sbox): sbox.simple_commit() #r3 # Delete a line. - open(iota_moved, 'w').write(line) + with open(iota_moved, 'w') as f: + f.write(line) sbox.simple_commit() #r4 expected_output = [ diff --git a/subversion/tests/cmdline/commit_tests.py b/subversion/tests/cmdline/commit_tests.py index 4f4a6f9..9bdb305 100755 --- a/subversion/tests/cmdline/commit_tests.py +++ b/subversion/tests/cmdline/commit_tests.py @@ -2823,7 +2823,8 @@ def commit_add_subadd(sbox): # prepare targets file targets = "A/D A/D/H A/D/H/chi A/D/H/omega A/D/H/psi".split() - open(targets_file, 'w').write("\n".join(targets)) + with open(targets_file, 'w') as f: + f.write("\n".join(targets)) # r2: rm A/D sbox.simple_rm('A/D') diff --git a/subversion/tests/cmdline/copy_tests.py b/subversion/tests/cmdline/copy_tests.py index 3bb8599..c87f36f 100755 --- a/subversion/tests/cmdline/copy_tests.py +++ b/subversion/tests/cmdline/copy_tests.py @@ -4333,7 +4333,7 @@ def copy_added_dir_with_copy(sbox): def copy_broken_symlink(sbox): """copy broken symlink""" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3303. ## + ## See https://issues.apache.org/jira/browse/SVN-3303. ## sbox.build() wc_dir = sbox.wc_dir diff --git a/subversion/tests/cmdline/dav-mirror-autocheck.sh b/subversion/tests/cmdline/dav-mirror-autocheck.sh index 298a8ba..7bc5e11 100755 --- a/subversion/tests/cmdline/dav-mirror-autocheck.sh +++ b/subversion/tests/cmdline/dav-mirror-autocheck.sh @@ -31,7 +31,7 @@ # # The set of changes sent through the system is currently # just the test case for issue 2939, using svnmucc -# http://subversion.tigris.org/issues/show_bug.cgi?id=2939 +# https://issues.apache.org/jira/browse/SVN-2939 # But of course, any svn traffic liable to break over # mirroring would be a good addition. # @@ -100,6 +100,7 @@ function setup_config() { say "setting up config: " $1 cat > "$1" <<__EOF__ +$LOAD_MOD_MPM $LOAD_MOD_LOG_CONFIG $LOAD_MOD_MIME $LOAD_MOD_UNIXD @@ -114,9 +115,30 @@ $LOAD_MOD_AUTHZ_CORE $LOAD_MOD_AUTHZ_USER $LOAD_MOD_AUTHZ_HOST +__EOF__ + +if "$HTTPD" -v | grep '/2\.[012]' >/dev/null; then + cat >> "$1" <<__EOF__ LockFile lock User $(id -un) Group $(id -gn) +__EOF__ +else +HTTPD_LOCK="$HTTPD_ROOT/lock" +mkdir "$HTTPD_LOCK" \ + || fail "couldn't create lock directory '$HTTPD_LOCK'" + cat >> "$1" <<__EOF__ +# worker and prefork MUST have a mpm-accept lockfile in 2.3.0+ +<IfModule worker.c> + Mutex "file:$HTTPD_LOCK" mpm-accept +</IfModule> +<IfModule prefork.c> + Mutex "file:$HTTPD_LOCK" mpm-accept +</IfModule> +__EOF__ +fi + +cat >> "$1" <<__EOF__ Listen ${TEST_PORT} ServerName localhost PidFile "${HTTPD_ROOT}/pid" @@ -133,6 +155,9 @@ MaxRequestsPerChild 0 <IfModule worker.c> ThreadsPerChild 8 </IfModule> +<IfModule event.c> + ThreadsPerChild 8 +</IfModule> MaxClients 16 HostNameLookups Off LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" format @@ -202,6 +227,12 @@ function usage() { SCRIPT=$(basename $0) +NO_TESTS= +if [ "x$1" = 'x--no-tests' ]; then + NO_TESTS=1 + shift +fi + if [ $# -ne 1 ] ; then usage fi @@ -249,7 +280,7 @@ HTPASSWD=$(get_prog_name htpasswd htpasswd2) \ SVN=$ABS_BUILDDIR/subversion/svn/svn SVNADMIN=$ABS_BUILDDIR/subversion/svnadmin/svnadmin SVNSYNC=$ABS_BUILDDIR/subversion/svnsync/svnsync -SVNMUCC=${SVNMUCC:-$ABS_BUILDDIR/tools/client-side/svnmucc/svnmucc} +SVNMUCC=$ABS_BUILDDIR/subversion/svnmucc/svnmucc SVNLOOK=$ABS_BUILDDIR/subversion/svnlook/svnlook [ -x $HTTPD ] || fail "HTTPD '$HTTPD' not executable" @@ -259,9 +290,7 @@ SVNLOOK=$ABS_BUILDDIR/subversion/svnlook/svnlook [ -x $SVNADMIN ] || fail "SVNADMIN $SVNADMIN not built" [ -x $SVNSYNC ] || fail "SVNSYNC $SVNSYNC not built" [ -x $SVNLOOK ] || fail "SVNLOOK $SVNLOOK not built" -[ -x $SVNMUCC ] \ - || fail SVNMUCC $SVNMUCC executable not built, needed for test. \ - \'cd $ABS_BUILDDIR\; make svnmucc\' to fix. +[ -x $SVNMUCC ] || fail "SVNMUCC $SVNMUCC not built" say HTTPD: $HTTPD say SVN: $SVN @@ -309,6 +338,10 @@ LOAD_MOD_AUTHN_FILE="$(get_loadmodule_config mod_authn_file)" \ LOAD_MOD_AUTHZ_USER="$(get_loadmodule_config mod_authz_user)" \ || fail "Authz_User module not found." } +if [ ${APACHE_MPM:+set} ]; then + LOAD_MOD_MPM=$(get_loadmodule_config mod_mpm_$APACHE_MPM) \ + || fail "MPM module not found" +fi if [ ${MODULE_PATH:+set} ]; then MOD_DAV_SVN="$MODULE_PATH/mod_dav_svn.so" @@ -365,7 +398,9 @@ $SVNADMIN create "$SLAVE_REPOS" || fail "create slave repos failed" $SVNADMIN dump "$MASTER_REPOS" | $SVNADMIN load "$SLAVE_REPOS" \ || fail "duplicate repositories failed" # make sure uuid's match -[ `cat "$SLAVE_REPOS/db/uuid"` = `cat "$MASTER_REPOS/db/uuid"` ] \ +read MASTER_UUID < "$MASTER_REPOS/db/uuid" +read SLAVE_UUID < "$SLAVE_REPOS/db/uuid" +[ "$SLAVE_UUID" = "$MASTER_UUID" ] \ || fail "master/slave uuid mismatch" # setup hooks: # slave allows revprop changes @@ -397,12 +432,18 @@ $SVNSYNC initialize --non-interactive "$SYNC_URL" "$MASTER_URL" \ --username=svnsync --password=svnsync \ || fail "svnsync initialize failed" +if [ $NO_TESTS ]; then + echo "MASTER_URL=$MASTER_URL" + echo "SLAVE_URL=$SLAVE_URL" + exit +fi + # OK, let's start testing! Commit changes to slave, expect # them to proxy through to the master, and then # svnsync back to the slave # # reproducible test case from: -# http://subversion.tigris.org/issues/show_bug.cgi?id=2939 +# https://issues.apache.org/jira/browse/SVN-2939 # BASE_URL="$SLAVE_URL" say running svnmucc test to $BASE_URL diff --git a/subversion/tests/cmdline/dav_tests.py b/subversion/tests/cmdline/dav_tests.py new file mode 100755 index 0000000..a99cdf5 --- /dev/null +++ b/subversion/tests/cmdline/dav_tests.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# +# dav_tests.py: testing connections to HTTP and DAV servers. +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import os, re + +# Our testing module +import svntest + +# (abbreviation) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco +Item = svntest.wc.StateItem + + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + + +#---------------------------------------------------------------------- + +@SkipUnless(svntest.main.is_ra_type_dav) +def connect_plain_http_server(sbox): + "connect to a non-DAV HTTP server" + expected_errors = svntest.verify.RegexListOutput([ + "^svn: E170013: Unable to connect to a repository at URL '[^']+'", + "^svn: E175003: The server at '[^']+' does not support the HTTP/DAV protocol" + ], False) + svntest.actions.run_and_verify_svn([], expected_errors, + 'info', svntest.main.non_dav_root_url) + +@SkipUnless(svntest.main.is_ra_type_dav) +def connect_other_dav_server(sbox): + "connect to a DAV server which is not an SVN server" + svntest.actions.run_and_verify_svn([], svntest.verify.AnyOutput, + 'info', svntest.main.other_dav_root_url) + +######################################################################## +# Run the tests + + +# list all tests here, starting with None: +test_list = [ None, + connect_plain_http_server, + connect_other_dav_server, + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. diff --git a/subversion/tests/cmdline/davautocheck.sh b/subversion/tests/cmdline/davautocheck.sh index 064feb1..7170b77 100755 --- a/subversion/tests/cmdline/davautocheck.sh +++ b/subversion/tests/cmdline/davautocheck.sh @@ -281,6 +281,9 @@ say "Using '$HTPASSWD'..." LOAD_MOD_DAV=$(get_loadmodule_config mod_dav) \ || fail "DAV module not found" +LOAD_MOD_DAV_FS=$(get_loadmodule_config mod_dav_fs) \ + || fail "Filesystem DAV module not found" + LOAD_MOD_LOG_CONFIG=$(get_loadmodule_config mod_log_config) \ || fail "log_config module not found" @@ -447,6 +450,7 @@ $LOAD_MOD_MIME $LOAD_MOD_ALIAS $LOAD_MOD_UNIXD $LOAD_MOD_DAV +$LOAD_MOD_DAV_FS LoadModule dav_svn_module "$MOD_DAV_SVN" $LOAD_MOD_AUTH $LOAD_MOD_AUTHN_CORE @@ -482,6 +486,13 @@ mkdir "$HTTPD_LOCK" \ __EOF__ fi +HTTPD_DAV="$HTTPD_ROOT/dav" +mkdir "$HTTPD_DAV" \ + || fail "couldn't create DAV lock directory '$HTTPD_DAV'" +cat >> "$HTTPD_CFG" <<__EOF__ +DavLockDB "$HTTPD_DAV/lock.db" +__EOF__ + if [ ${USE_SSL:+set} ]; then cat >> "$HTTPD_CFG" <<__EOF__ SSLEngine on @@ -525,6 +536,15 @@ CustomLog "$HTTPD_ROOT/ops" "%t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e" #Require all granted </Directory> +Alias /nodavroot $ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/nodavroot +<Directory $ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/nodavroot> +</Directory> + +Alias /fsdavroot $ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/fsdavroot +<Directory $ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/fsdavroot> + DAV filesystem +</Directory> + <Location /svn-test-work/repositories> __EOF__ location_common() { diff --git a/subversion/tests/cmdline/depth_tests.py b/subversion/tests/cmdline/depth_tests.py index 1277594..18ce379 100755 --- a/subversion/tests/cmdline/depth_tests.py +++ b/subversion/tests/cmdline/depth_tests.py @@ -1073,7 +1073,7 @@ def commit_depth_immediates(sbox): # Message-ID: <46968831.2070906@collab.net> # Date: Thu, 12 Jul 2007 15:59:45 -0400 # - # See also http://subversion.tigris.org/issues/show_bug.cgi?id=2882. + # See also https://issues.apache.org/jira/browse/SVN-2882. # # Outline of the test: # ==================== diff --git a/subversion/tests/cmdline/diff_tests.py b/subversion/tests/cmdline/diff_tests.py index 905bce3..63caa24 100755 --- a/subversion/tests/cmdline/diff_tests.py +++ b/subversion/tests/cmdline/diff_tests.py @@ -36,7 +36,6 @@ from svntest import err, wc from prop_tests import binary_mime_type_on_text_file_warning from svntest.verify import make_diff_header, make_no_diff_deleted_header, \ - make_diff_header, make_no_diff_deleted_header, \ make_git_diff_header, make_diff_prop_header, \ make_diff_prop_val, make_diff_prop_deleted, \ make_diff_prop_added, make_diff_prop_modified @@ -2712,7 +2711,7 @@ def diff_ignore_eolstyle(sbox): " Bb\n", "-Cc\n", "+Cc\n", - "\ No newline at end of file\n" ] + "\\ No newline at end of file\n" ] svntest.actions.run_and_verify_svn(expected_output, [], 'diff', '-x', '--ignore-eol-style', @@ -3855,7 +3854,7 @@ def diff_arbitrary_files_and_dirs(sbox): sbox.build() wc_dir = sbox.wc_dir - # diff iota with A/mu + # diff files (iota with A/mu) expected_output = make_diff_header("iota", "working copy", "working copy", "iota", "A/mu") + [ "@@ -1 +1 @@\n", @@ -3866,7 +3865,11 @@ def diff_arbitrary_files_and_dirs(sbox): 'diff', '--old', sbox.ospath('iota'), '--new', sbox.ospath('A/mu')) - # diff A/B/E with A/D + # diff dirs (A/B/E with A/D) + # .../gamma is to show as replaced; .../beta is to show as modified + sbox.simple_mkdir('A/B/E/gamma') + sbox.simple_propset('p', 'v', 'A/B/E/gamma') + sbox.simple_add_text("This is a different beta file.\n", 'A/D/beta') expected_output = make_diff_header("G/pi", "nonexistent", "working copy", "B/E", "D") + [ "@@ -0,0 +1 @@\n", @@ -3896,11 +3899,16 @@ def diff_arbitrary_files_and_dirs(sbox): "@@ -1 +0,0 @@\n", "-This is the file 'alpha'.\n" ] + make_diff_header("beta", "working copy", - "nonexistent", "B/E", "D") + [ - "@@ -1 +0,0 @@\n", - "-This is the file 'beta'.\n" - ] + make_diff_header("gamma", "nonexistent", "working copy", "B/E", "D") + [ + "@@ -1 +1 @@\n", + "-This is the file 'beta'.\n", + "+This is a different beta file.\n" + ] + make_diff_header("gamma", "working copy", + "nonexistent", "B/E", "D") \ + + make_diff_prop_header("gamma") \ + + make_diff_prop_deleted("p", "v") \ + + make_diff_header("gamma", "nonexistent", + "working copy", "B/E", "D") + [ "@@ -0,0 +1 @@\n", "+This is the file 'gamma'.\n" ] @@ -5160,6 +5168,90 @@ def diff_unversioned_files_git(sbox): '--old', sbox.ospath('foo'), '--new', sbox.ospath('A/bar')) +# Summary diff with a repository source side and a local copy target side. +# This particular combination crashed in 1.10.0 and earlier releases. +def diff_summary_repo_wc_local_copy(sbox): + "diff summary repo wc local copy" + sbox.build() + wc_dir = sbox.wc_dir + + sbox.simple_copy('iota', 'iota2') + sbox.simple_append('iota2', 'hello\n') + expected_diff = svntest.wc.State(wc_dir, { + 'iota': Item(status='M '), + }) + svntest.actions.run_and_verify_diff_summarize( + expected_diff, + '--old=' + sbox.ospath('iota') + '@HEAD', + '--new=' + sbox.ospath('iota2')) + +# Summary diff with a repository source side and a local copy target side. +# Svn reported the unmodified copy as modified in 1.10.0 and earlier releases. +@XFail() +def diff_summary_repo_wc_local_copy_unmodified(sbox): + "diff summary repo wc local copy unmodified" + sbox.build() + wc_dir = sbox.wc_dir + + sbox.simple_copy('iota', 'iota2') + expected_diff = svntest.wc.State(wc_dir, { + }) + svntest.actions.run_and_verify_diff_summarize( + expected_diff, + '--old=' + sbox.ospath('iota') + '@HEAD', + '--new=' + sbox.ospath('iota2')) + +# Fails with "Can't open file '.../iota': Too many levels of symbolic links" +# on Unix. +@XFail() +@Skip(svntest.main.is_os_windows) +def diff_file_replaced_by_symlink(sbox): + "diff base vs working: symlink replaces a file" + sbox.build(read_only=True) + wc_dir = sbox.wc_dir + + iota_path = sbox.ospath('iota') + os.remove(iota_path) + + # create a symlink pointing to itself + # alternatively it could point to a non-existing path + sbox.simple_symlink('iota', 'iota') + + # TODO: add a full expected output + expected_output = svntest.verify.AnyOutput + svntest.actions.run_and_verify_svn(expected_output, [], 'diff', wc_dir) + +# Test 'svn diff --git' with a copy. +# +# When this diff is rooted at a path below the repository root directory, +# it errored out while printing the git diff header, due to confusion of +# diff-relative and repository-relative copyfrom paths. +@XFail() +def diff_git_format_copy(sbox): + "diff git format copy" + sbox.build(create_wc=False) + svntest.actions.run_and_verify_svn(None, [], 'checkout', + sbox.repo_url + '/A/B', + sbox.wc_dir) + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + sbox.simple_copy('E/alpha', 'alpha_copied') + sbox.simple_append('alpha_copied', "This is a copy of 'alpha'.\n") + + expected_output = \ + make_git_diff_header('alpha_copied', 'A/B/alpha_copied', + "revision 1", "working copy", + copyfrom_path="A/B/E/alpha", + copyfrom_rev='1', cp=True, + text_changes=True) + [ + "@@ -1 +1,2 @@\n", + " This is the file 'alpha'.\n", + "+This is a copy of 'alpha'.\n", + ] + + svntest.actions.run_and_verify_svn(expected_output, [], 'diff', + '--git', '.') ######################################################################## #Run the tests @@ -5257,6 +5349,10 @@ test_list = [ None, diff_symlinks, diff_peg_resolve, diff_unversioned_files_git, + diff_summary_repo_wc_local_copy, + diff_summary_repo_wc_local_copy_unmodified, + diff_file_replaced_by_symlink, + diff_git_format_copy, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/entries_tests.py b/subversion/tests/cmdline/entries_tests.py index 0fcdc1a..78ba037 100755 --- a/subversion/tests/cmdline/entries_tests.py +++ b/subversion/tests/cmdline/entries_tests.py @@ -121,14 +121,17 @@ def basic_entries(sbox): G_path, iota_path) # Add a file over the DELETED 'alpha'. It should be schedule-add. - open(alpha_path, 'w').write('New alpha contents\n') + with open(alpha_path, 'w') as f: + f.write('New alpha contents\n') # Delete 'beta', then add a file over it. Should be schedule-replace. svntest.actions.run_and_verify_svn(None, [], 'rm', beta_path) - open(beta_path, 'w').write('New beta contents\n') + with open(beta_path, 'w') as f: + f.write('New beta contents\n') # Plain old add. Should have revision == 0. - open(added_path, 'w').write('Added file contents\n') + with open(added_path, 'w') as f: + f.write('Added file contents\n') svntest.actions.run_and_verify_svn(None, [], 'add', alpha_path, beta_path, added_path) diff --git a/subversion/tests/cmdline/export_tests.py b/subversion/tests/cmdline/export_tests.py index a8ac7b8..c524ff3 100755 --- a/subversion/tests/cmdline/export_tests.py +++ b/subversion/tests/cmdline/export_tests.py @@ -610,7 +610,8 @@ def export_file_overwrite_fails(sbox): os.mkdir(tmpdir) # Run it for source local - open(os.path.join(tmpdir, 'iota'), 'w').write(not_iota_contents) + with open(os.path.join(tmpdir, 'iota'), 'w') as f: + f.write(not_iota_contents) svntest.actions.run_and_verify_svn([], '.*exist.*', 'export', iota_path, tmpdir) @@ -621,7 +622,8 @@ def export_file_overwrite_fails(sbox): svntest.actions.verify_disk(tmpdir, expected_disk) # Run it for source URL - open(os.path.join(tmpdir, 'iota'), 'w').write(not_iota_contents) + with open(os.path.join(tmpdir, 'iota'), 'w') as f: + f.write(not_iota_contents) svntest.actions.run_and_verify_svn([], '.*exist.*', 'export', iota_url, tmpdir) @@ -721,7 +723,7 @@ def export_working_copy_ignoring_keyword_translation(sbox): def export_with_url_unsafe_characters(sbox): "export file with URL unsafe characters" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3683 ## + ## See https://issues.apache.org/jira/browse/SVN-3683 ## sbox.build() wc_dir = sbox.wc_dir @@ -904,14 +906,16 @@ def export_file_overwrite_with_force(sbox): }) # Run it for WC export - open(os.path.join(tmpdir, 'iota'), 'w').write(not_iota_contents) + with open(os.path.join(tmpdir, 'iota'), 'w') as f: + f.write(not_iota_contents) svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], 'export', '--force', iota_path, tmpdir) svntest.actions.verify_disk(tmpdir, expected_disk) # Run it for URL export - open(os.path.join(tmpdir, 'iota'), 'w').write(not_iota_contents) + with open(os.path.join(tmpdir, 'iota'), 'w') as f: + f.write(not_iota_contents) svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], 'export', '--force', iota_url, tmpdir) diff --git a/subversion/tests/cmdline/externals_tests.py b/subversion/tests/cmdline/externals_tests.py index c0ac029..ba1320c 100755 --- a/subversion/tests/cmdline/externals_tests.py +++ b/subversion/tests/cmdline/externals_tests.py @@ -1146,7 +1146,7 @@ def external_into_path_with_spaces(sbox): repo_url = sbox.repo_url ext = '^/A/D "A/copy of D"\n' +\ - '^/A/D A/another\ copy\ of\ D' + '^/A/D A/another\\ copy\\ of\\ D' change_external(wc_dir, ext) expected_output = svntest.wc.State(wc_dir, { @@ -2818,7 +2818,7 @@ def remap_file_external_with_prop_del(sbox): # Now update to bring the new external down. # This previously segfaulted as described in - # http://subversion.tigris.org/issues/show_bug.cgi?id=4093#desc1 + # https://issues.apache.org/jira/browse/SVN-4093#desc1 svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) @@ -3871,12 +3871,14 @@ def copy_pin_externals_whitespace_dir(sbox): extdef = sbox.get_tempname('extdef') info = sbox.get_tempname('info') - open(extdef, 'w').write( + with open(extdef, 'w') as f: + f.write( '"' + ss_path +'/deps/sqlite" ext/sqlite\n' + '"^/deps/A P R" \'ext/A P R\'\n' + - '^/deps/B\ D\ B\' ext/B\ D\ B\'\n' + + '^/deps/B\\ D\\ B\' ext/B\\ D\\ B\'\n' + repo_url + '/deps/wors%23+t ext/wors#+t') - open(info, 'w').write('info\n') + with open(info, 'w') as f: + f.write('info\n') svntest.actions.run_and_verify_svnmucc(None, [], '-U', repo_url, 'mkdir', 'trunk', @@ -4408,7 +4410,17 @@ def update_dir_external_exclude(sbox): # Create an external in r2 sbox.simple_propset('svn:externals', '^/A/D/H X', 'A/B/E') sbox.simple_commit() - sbox.simple_update() + + # Update to fetch externals + expected_output = svntest.wc.State(sbox.wc_dir, { + 'A/B/E/X/chi' : Item(status='A '), + 'A/B/E/X/omega' : Item(status='A '), + 'A/B/E/X/psi' : Item(status='A '), + }) + svntest.actions.run_and_verify_update(sbox.wc_dir, + expected_output, None, None, + [], False, + sbox.ospath('A/B/E')) # Now make A/B/E shallow by updating with "--set-depth exclude" expected_output = svntest.wc.State(sbox.wc_dir, { diff --git a/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout b/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout index 05f0aa8..ff73b51 100644 --- a/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout +++ b/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout @@ -47,9 +47,14 @@ Available subcommands: unlock update (up) upgrade - x-shelve (shelve) - x-unshelve (unshelve) - x-shelves (shelves) + x-shelf-diff + x-shelf-drop + x-shelf-list (x-shelves) + x-shelf-list-by-paths + x-shelf-log + x-shelf-save + x-shelve + x-unshelve Subversion is a tool for version control. For additional information, see http://subversion.apache.org/ diff --git a/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout b/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout index 5dddc70..2b81052 100644 --- a/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout +++ b/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout @@ -84,6 +84,10 @@ usage: 1. log [PATH][@REV] was created: svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo + Show all log messages for commits between the tags ^/tags/2.0 and + ^/tags/3.0; assuming that tag 2.0 was created in revision 100: + svn log -rHEAD:100 ^/tags/3.0 + If ^/trunk/foo.c was moved to ^/trunk/bar.c' in revision 22, 'svn log -v' shows a deletion and a copy in its changed paths list, such as: D /trunk/foo.c diff --git a/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout b/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout index 05f0aa8..ff73b51 100644 --- a/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout +++ b/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout @@ -47,9 +47,14 @@ Available subcommands: unlock update (up) upgrade - x-shelve (shelve) - x-unshelve (unshelve) - x-shelves (shelves) + x-shelf-diff + x-shelf-drop + x-shelf-list (x-shelves) + x-shelf-list-by-paths + x-shelf-log + x-shelf-save + x-shelve + x-unshelve Subversion is a tool for version control. For additional information, see http://subversion.apache.org/ diff --git a/subversion/tests/cmdline/iprop_authz_tests.py b/subversion/tests/cmdline/iprop_authz_tests.py index 835cd37..c2a9d10 100755 --- a/subversion/tests/cmdline/iprop_authz_tests.py +++ b/subversion/tests/cmdline/iprop_authz_tests.py @@ -105,7 +105,7 @@ def iprops_authz(sbox): write_authz_file(sbox, { "/" : svntest.main.wc_author + "=rw", "/A/D/H/psi" : svntest.main.wc_author + "=",}) - if sbox.repo_url.startswith("http"): + if svntest.main.is_ra_type_dav(): expected_err = ".*[Ff]orbidden.*" else: expected_err = ".*svn: E170001: Authorization failed.*" diff --git a/subversion/tests/cmdline/lock_tests.py b/subversion/tests/cmdline/lock_tests.py index cd8e0d2..3e2952c 100755 --- a/subversion/tests/cmdline/lock_tests.py +++ b/subversion/tests/cmdline/lock_tests.py @@ -1571,7 +1571,8 @@ def cp_isnt_ro(sbox): mu2_path = sbox.ospath('A/mu2') mu3_path = sbox.ospath('A/mu3') kappa_path = sbox.ospath('kappa') - open(kappa_path, 'w').write("This is the file 'kappa'.\n") + with open(kappa_path, 'w') as f: + f.write("This is the file 'kappa'.\n") ## added file sbox.simple_add('kappa') @@ -2251,7 +2252,6 @@ def dav_lock_refresh(sbox): if r.status != httplib.OK: raise svntest.Failure('Lock refresh failed: %d %s' % (r.status, r.reason)) -@SkipUnless(svntest.main.is_ra_type_dav) def delete_locked_file_with_percent(sbox): "lock and delete a file called 'a %( ) .txt'" diff --git a/subversion/tests/cmdline/log_tests.py b/subversion/tests/cmdline/log_tests.py index 484103a..559dfb5 100755 --- a/subversion/tests/cmdline/log_tests.py +++ b/subversion/tests/cmdline/log_tests.py @@ -1592,7 +1592,7 @@ def merge_sensitive_log_added_mergeinfo_replaces_inherited(sbox): # a merge results in added explicit mergeinfo on a path, but that # path previously inherited mergeinfo (rather than had no explicit # or inherited mergeinfo). See issue #3235, specifically - # http://subversion.tigris.org/issues/show_bug.cgi?id=3235#desc8. + # https://issues.apache.org/jira/browse/SVN-3235#desc8. sbox.build() wc_dir = sbox.wc_dir @@ -1752,7 +1752,7 @@ def merge_sensitive_log_added_mergeinfo_replaces_inherited(sbox): def merge_sensitive_log_propmod_merge_inheriting_path(sbox): "log -g and simple propmod to merge-inheriting path" - # Issue #3285 (http://subversion.tigris.org/issues/show_bug.cgi?id=3285) + # Issue #3285 (https://issues.apache.org/jira/browse/SVN-3285) sbox.build() wc_dir = sbox.wc_dir @@ -2166,13 +2166,13 @@ def log_diff(sbox): + [ "@@ -1 +1,2 @@\n", " This is the file 'beta'.\n", "+9\n", - "\ No newline at end of file\n", + "\\ No newline at end of file\n", ] ] r8diff = [ make_diff_header('A2/D/G/rho', 'nonexistent', 'revision 8') + [ "@@ -0,0 +1 @@\n", "+88\n", - "\ No newline at end of file\n", + "\\ No newline at end of file\n", ] ] log_chain = parse_log_output(output, with_diffs=True) diff --git a/subversion/tests/cmdline/merge_authz_tests.py b/subversion/tests/cmdline/merge_authz_tests.py index 8e14089..b26e9c7 100755 --- a/subversion/tests/cmdline/merge_authz_tests.py +++ b/subversion/tests/cmdline/merge_authz_tests.py @@ -486,7 +486,7 @@ def mergeinfo_and_skipped_paths(sbox): def merge_fails_if_subtree_is_deleted_on_src(sbox): "merge fails if subtree is deleted on src" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2876. ## + ## See https://issues.apache.org/jira/browse/SVN-2876. ## # Create a WC sbox.build() @@ -613,7 +613,7 @@ def reintegrate_fails_if_no_root_access(sbox): # should be able to reintegrate, regardless of what authorization # they have to parents of the source and target. # - # See http://subversion.tigris.org/issues/show_bug.cgi?id=3242#desc78 + # See https://issues.apache.org/jira/browse/SVN-3242#desc78 # Some paths we'll care about wc_dir = sbox.wc_dir diff --git a/subversion/tests/cmdline/merge_automatic_tests.py b/subversion/tests/cmdline/merge_automatic_tests.py index f4bb231..fa00e04 100755 --- a/subversion/tests/cmdline/merge_automatic_tests.py +++ b/subversion/tests/cmdline/merge_automatic_tests.py @@ -1163,7 +1163,7 @@ def effective_sync_results_in_reintegrate(sbox): # Now try an explicit --reintegrate merge from ^/branch to A. # This should work because since the resolution of - # http://subversion.tigris.org/issues/show_bug.cgi?id=3577 + # https://issues.apache.org/jira/browse/SVN-3577 # if B is *effectively* synced with A, then B can be reintegrated # to A. sbox.simple_update() diff --git a/subversion/tests/cmdline/merge_reintegrate_tests.py b/subversion/tests/cmdline/merge_reintegrate_tests.py index 7a27373..1479400 100755 --- a/subversion/tests/cmdline/merge_reintegrate_tests.py +++ b/subversion/tests/cmdline/merge_reintegrate_tests.py @@ -1363,7 +1363,7 @@ def reintegrate_with_subtree_mergeinfo(sbox): # how can any prop changes be merged to it? The answer is that # the merge code does some quiet housekeeping, merging gamma_moved's # inherited mergeinfo into its incoming mergeinfo, see - # http://subversion.tigris.org/issues/show_bug.cgi?id=4309 + # https://issues.apache.org/jira/browse/SVN-4309 # This test is not covering issue #4309 so we let the current # behavior pass. # r17 - B) Synch merge from A to A_COPY diff --git a/subversion/tests/cmdline/merge_tests.py b/subversion/tests/cmdline/merge_tests.py index a67dada..22b02dd 100755 --- a/subversion/tests/cmdline/merge_tests.py +++ b/subversion/tests/cmdline/merge_tests.py @@ -697,7 +697,7 @@ def simple_property_merges(sbox): def merge_similar_unrelated_trees(sbox): "merging similar trees ancestrally unrelated" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=1249. ## + ## See https://issues.apache.org/jira/browse/SVN-1249. ## sbox.build() wc_dir = sbox.wc_dir @@ -3939,7 +3939,7 @@ def avoid_repeated_merge_on_subtree_with_merge_info(sbox): check_props=True) # Test for part of Issue #2821, see - # http://subversion.tigris.org/issues/show_bug.cgi?id=2821#desc22 + # https://issues.apache.org/jira/browse/SVN-2821#desc22 # # Revert all local changes. svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) @@ -6007,7 +6007,7 @@ def foreign_repos_does_not_update_mergeinfo(sbox): def avoid_reflected_revs(sbox): "avoid repeated merges for cyclic merging" - # See <http://subversion.tigris.org/issues/show_bug.cgi?id=2897>. + # See <https://issues.apache.org/jira/browse/SVN-2897>. # # This test cherry-picks some changes (all of them, in fact) from the # parent branch 'A' to the child branch 'A_COPY', and then tries to @@ -8294,7 +8294,7 @@ def cherry_picking(sbox): def propchange_of_subdir_raises_conflict(sbox): "merge of propchange on subdir raises conflict" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2969. ## + ## See https://issues.apache.org/jira/browse/SVN-2969. ## # Create a WC with a single branch sbox.build() @@ -8515,7 +8515,7 @@ def reverse_merge_prop_add_on_child(sbox): def merge_target_with_non_inheritable_mergeinfo(sbox): "merge target with non inheritable mergeinfo" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2970. ## + ## See https://issues.apache.org/jira/browse/SVN-2970. ## # Create a WC with a single branch sbox.build() @@ -8547,7 +8547,7 @@ def merge_target_with_non_inheritable_mergeinfo(sbox): expected_output = wc.State(A_COPY_B_path, { 'lambda' : Item(status='U '), }) - # Issue #3642 http://subversion.tigris.org/issues/show_bug.cgi?id=3642 + # Issue #3642 https://issues.apache.org/jira/browse/SVN-3642 # # We don't expect A_COPY/B/F to have mergeinfo recorded on it because # not only is it unaffected by the merge at depth immediates, it could @@ -8820,7 +8820,7 @@ def merge_from_renamed_branch_fails_while_avoiding_repeat_merge(sbox): #Merge r4 from A/RENAMED_C to A/C #Merge r2:5 from A/RENAMED_C to A/C <-- This fails tracked via #3032. - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3032. ## + ## See https://issues.apache.org/jira/browse/SVN-3032. ## # Create a WC with a single branch sbox.build() @@ -9250,7 +9250,7 @@ def new_subtrees_should_not_break_merge(sbox): # so we expect only subtree merges on A_COPY/D, A_COPY_D_H, and # A_COPY/D/H/nu. The fact that A/D/H/nu doesn't exist at r6 should not cause # the merge to fail -- see - # http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc7. + # https://issues.apache.org/jira/browse/SVN-3067#desc7. expected_output = wc.State(A_COPY_path, { 'D/H/omega': Item(status='U '), }) @@ -10495,7 +10495,7 @@ def reverse_merge_away_all_mergeinfo(sbox): # Another test for issue #3067: 'subtrees with intersecting mergeinfo, # that don't exist at the start of a merge range shouldn't break the # merge'. Specifically see -# http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc5 +# https://issues.apache.org/jira/browse/SVN-3067#desc5 @SkipUnless(server_has_mergeinfo) @Issues(3138,3067,4217) def dont_merge_revs_into_subtree_that_predate_it(sbox): @@ -10614,7 +10614,7 @@ def dont_merge_revs_into_subtree_that_predate_it(sbox): # Cherry harvest all eligible revisions from 'A/D/H' to 'H_COPY'. # # This is where we see the problem described in - # http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc5. + # https://issues.apache.org/jira/browse/SVN-3067#desc5. # # Use run_and_verify_svn() because run_and_verify_merge*() require # explicit revision ranges. @@ -11786,7 +11786,7 @@ def subtree_source_missing_in_requested_range(sbox): # Another test for issue #3067: 'subtrees that don't exist at the start # or end of a merge range shouldn't break the merge' # -# See http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc34 +# See https://issues.apache.org/jira/browse/SVN-3067#desc34 @Issue(3067) @SkipUnless(server_has_mergeinfo) def subtrees_with_empty_mergeinfo(sbox): @@ -13305,7 +13305,7 @@ def no_self_referential_filtering_on_added_path(sbox): # how can any prop changes be merged to it? The answer is that # the merge code does some quiet housekeeping, merging C_MOVED's # inherited mergeinfo into its incoming mergeinfo, see - # http://subversion.tigris.org/issues/show_bug.cgi?id=4309 + # https://issues.apache.org/jira/browse/SVN-4309 # This test is not covering issue #4309 so we let the current # behavior pass. expected_mergeinfo_output = wc.State(A_COPY_2_path, { @@ -13374,7 +13374,7 @@ def no_self_referential_filtering_on_added_path(sbox): #---------------------------------------------------------------------- # Test for issue #3324 -# http://subversion.tigris.org/issues/show_bug.cgi?id=3324 +# https://issues.apache.org/jira/browse/SVN-3324 @Issue(3324) @SkipUnless(server_has_mergeinfo) def merge_range_prior_to_rename_source_existence(sbox): @@ -13881,7 +13881,7 @@ def dont_merge_gaps_in_history(sbox): #---------------------------------------------------------------------- # Test for issue #3432 'Merge can record mergeinfo from natural history -# gaps'. See http://subversion.tigris.org/issues/show_bug.cgi?id=3432 +# gaps'. See https://issues.apache.org/jira/browse/SVN-3432 @Issue(3432) @SkipUnless(server_has_mergeinfo) def handle_gaps_in_implicit_mergeinfo(sbox): @@ -15122,7 +15122,7 @@ def foreign_repos_del_and_props(sbox): #---------------------------------------------------------------------- # Test for issue #3642 'immediate depth merges don't create proper subtree -# mergeinfo'. See http://subversion.tigris.org/issues/show_bug.cgi?id=3642 +# mergeinfo'. See https://issues.apache.org/jira/browse/SVN-3642 @Issue(3642) def immediate_depth_merge_creates_minimal_subtree_mergeinfo(sbox): "no spurious mergeinfo from immediate depth merges" @@ -15817,7 +15817,7 @@ def subtree_merges_inherit_invalid_working_mergeinfo(sbox): #---------------------------------------------------------------------- # Test for issue #3686 'executable flag not correctly set on merge' -# See http://subversion.tigris.org/issues/show_bug.cgi?id=3686 +# See https://issues.apache.org/jira/browse/SVN-3686 @Issue(3686) @SkipUnless(server_has_mergeinfo) @SkipUnless(svntest.main.is_posix_os) diff --git a/subversion/tests/cmdline/merge_tree_conflict_tests.py b/subversion/tests/cmdline/merge_tree_conflict_tests.py index 4f1805b..344c2d2 100755 --- a/subversion/tests/cmdline/merge_tree_conflict_tests.py +++ b/subversion/tests/cmdline/merge_tree_conflict_tests.py @@ -540,7 +540,7 @@ def merge_add_over_versioned_file_conflicts(sbox): def mergeinfo_recording_in_skipped_merge(sbox): "mergeinfo recording in skipped merge" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2829. ## + ## See https://issues.apache.org/jira/browse/SVN-2829. ## # Create a WC with a single branch sbox.build() @@ -705,7 +705,7 @@ def del_differing_file(sbox): def tree_conflicts_and_obstructions(sbox): "tree conflicts and obstructions" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3146. ## + ## See https://issues.apache.org/jira/browse/SVN-3146. ## sbox.build() wc_dir = sbox.wc_dir diff --git a/subversion/tests/cmdline/mergeinfo_tests.py b/subversion/tests/cmdline/mergeinfo_tests.py index 328a9f2..fccf5fb 100755 --- a/subversion/tests/cmdline/mergeinfo_tests.py +++ b/subversion/tests/cmdline/mergeinfo_tests.py @@ -196,7 +196,7 @@ def mergeinfo_on_unknown_url(sbox): # Test for issue #3126 'svn mergeinfo shows too few or too many # eligible revisions'. Specifically -# http://subversion.tigris.org/issues/show_bug.cgi?id=3126#desc5. +# https://issues.apache.org/jira/browse/SVN-3126#desc5. @SkipUnless(server_has_mergeinfo) @Issue(3126) def non_inheritable_mergeinfo(sbox): diff --git a/subversion/tests/cmdline/mod_dav_svn_tests.py b/subversion/tests/cmdline/mod_dav_svn_tests.py index db30533..db30533 100644..100755 --- a/subversion/tests/cmdline/mod_dav_svn_tests.py +++ b/subversion/tests/cmdline/mod_dav_svn_tests.py diff --git a/subversion/tests/cmdline/move_tests.py b/subversion/tests/cmdline/move_tests.py index 1542af9..6da4a1d 100755 --- a/subversion/tests/cmdline/move_tests.py +++ b/subversion/tests/cmdline/move_tests.py @@ -1261,7 +1261,7 @@ def nested_replaces(sbox): ' D /A/B/C/Y', ])) expected_output = svntest.verify.UnorderedRegexListOutput(escaped - + [ '^-', '^r2', '^-', '^Changed paths:', ]) + + [ '^--*', '^r2.*', '^--*', '^Changed paths:', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-qvr2', repo_url) diff --git a/subversion/tests/cmdline/patch_tests.py b/subversion/tests/cmdline/patch_tests.py index 5d41dd6..3cd4dd7 100755 --- a/subversion/tests/cmdline/patch_tests.py +++ b/subversion/tests/cmdline/patch_tests.py @@ -4246,7 +4246,7 @@ def patch_git_with_index_line(sbox): "+++ b/src/tools/ConsoleRunner/hi.txt\n", "@@ -0,0 +1 @@\n", "+hihihihihihi\n", - "\ No newline at end of file\n", + "\\ No newline at end of file\n", ] svntest.main.file_write(patch_file_path, ''.join(unidiff_patch)) @@ -4413,7 +4413,7 @@ def patch_replace_dir_with_file_and_vv(sbox): "+++ A/D\t(working copy)\n", "@@ -0,0 +1 @@\n", "+New file\n", - "\ No newline at end of file\n", + "\\ No newline at end of file\n", # Add iota as directory "Index: iota\n", @@ -4426,7 +4426,7 @@ def patch_replace_dir_with_file_and_vv(sbox): "Added: k\n", "## -0,0 +1 ##\n", "+v\n", - "\ No newline at end of property\n", + "\\ No newline at end of property\n", ])) expected_output = wc.State(wc_dir, { @@ -6541,7 +6541,7 @@ def patch_prop_madness(sbox): "Property: del_n\n" "## -1,1 +0,0 ##\n" "-no-eol\n" - "\ No newline at end of property\n" + "\\ No newline at end of property\n" % (sbox.path('iota'), sbox.path('iota'))), }) @@ -7791,6 +7791,225 @@ def patch_merge(sbox): expected_disk, None, expected_skip) +def patch_mergeinfo_in_regular_prop_format(sbox): + "patch mergeinfo in regular prop format" + + sbox.build() + wc_dir = sbox.wc_dir + strip_count = wc_dir.count(os.path.sep)+1 + + sbox.simple_copy('A/B/E', 'E') + sbox.simple_append('A/B/E/alpha', 'extra\nlines\n') + sbox.simple_commit() + + sbox.simple_propset('a', 'A', 'E') # 'a' < 'svn:mergeinfo' + sbox.simple_propset('z', 'Z', 'E') # 'z' > 'svn:mergeinfo' + + svntest.actions.run_and_verify_svn(None, [], + 'merge', '^/A/B/E', sbox.ospath('E')) + # Rename 'svn:mergeinfo' to 'svn_mergeinfo' so that 'diff' doesn't + # pretty-print it; then rename it back before we run it through 'patch'. + # (Alternatively, we could disable pretty-printing when we implement a + # command-line switch to do so.) + mergeinfo_value = sbox.simple_propget('svn:mergeinfo', 'E') + sbox.simple_propdel('svn:mergeinfo', 'E') + sbox.simple_propset('svn_mergeinfo', mergeinfo_value, 'E') + + _, diff, _ = svntest.actions.run_and_verify_svn(None, [], + 'diff', wc_dir) + diff = re.sub('svn_mergeinfo', 'svn:mergeinfo', ''.join(diff)) + + sbox.simple_revert('E', 'E/alpha') + + patch = sbox.get_tempname('recurse.patch') + svntest.main.file_write(patch, diff, mode='wb') + + expected_output = wc.State(wc_dir, { + 'E' : Item(status=' U'), + 'E/alpha' : Item(status='U '), + }) + expected_skip = wc.State(wc_dir, {}) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'E' : Item(status=' M', wc_rev='2'), + 'E/alpha' : Item(status='M ', wc_rev='2'), + 'E/beta' : Item(status=' ', wc_rev='2'), + }) + expected_status.tweak('A/B/E/alpha', wc_rev=2) + expected_disk = svntest.main.greek_state.copy() + expected_disk.tweak('A/B/E/alpha', contents="This is the file 'alpha'.\nextra\nlines\n") + expected_disk.add({ + 'E' : Item(props={'a': 'A', + # here is the correctly patched mergeinfo + 'svn:mergeinfo': '/A/B/E:2', + 'z': 'Z'}), + 'E/beta' : Item(contents="This is the file 'beta'.\n"), + 'E/alpha' : Item(contents="This is the file 'alpha'.\nextra\nlines\n"), + }) + + svntest.actions.run_and_verify_patch(wc_dir, patch, + expected_output, expected_disk, + expected_status, expected_skip, + [], True, True, + '--strip', strip_count) + +@XFail() +def patch_empty_prop(sbox): + "patch empty prop" + sbox.build(empty=True) + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + wc_dir = '' + + # start with a file with an empty prop + sbox.simple_add_text('', 'f') + sbox.simple_propset('p', '', 'f') + sbox.simple_commit() + + # a patch that modifies the prop to a non-empty value + unidiff_patch = [ + "--- f\n", + "+++ f\n", + "\n", + "Property changes on: f\n", + "___________________________________________________________________\n", + "Modified: p\n", + "## -0,0 +1 ##\n", + "+v\n", + ] + + trailing_eol = False + if trailing_eol: + value = "v\n" + else: + value = "v" + unidiff_patch += ['\ No newline at end of property\n'] + + patch_file_path = sbox.get_tempname('my.patch') + svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb') + + expected_output = [ + ' U %s\n' % sbox.ospath('f'), + ] + + expected_disk = svntest.wc.State(wc_dir, {}) + expected_disk.add({'f': Item(props={'p' : value})}) + expected_status = svntest.wc.State(wc_dir, {}) + expected_status.add({'f': Item(status=' M')}) + expected_skip = wc.State('', { }) + + svntest.actions.run_and_verify_patch(wc_dir, patch_file_path, + expected_output, + expected_disk, + expected_status, + expected_skip, + None, # expected err + 1, # check-props + False, # dry-run + ) + + svntest.actions.check_prop('p', wc_dir, [value.encode()]) + + os.chdir(was_cwd) + +# Test that 'patch' can apply a patch that modifies properties on the root +# of a WC and/or the root of a repository. This test uses the format of +# diff output produced by 'svn diff --git' in svn <= 1.10.0: paths are given +# as 'a/' and 'b/' and 'Property changes on: ' (with no following '.'). +# +# See dev@ email thread 2018-07-09 from Dmitry Pavlenko, +# '[PATCH] can't "svn patch" working copy root if the patch is in --git format', +# https://lists.apache.org/thread.html/d1d9811ca36fac8cabb9339634840099e22811beac505be2ea59f19f@%3Cdev.subversion.apache.org%3E +@XFail() +def patch_git_wcroot(sbox): + "patch working copy root" + sbox.build(empty=True) + wc_dir = sbox.wc_dir + + git_patch = [ "Index: .\n", + "===================================================================\n", + "diff --git a/ b/\n", + "--- a/ (revision 0)\n", + "+++ b/ (working copy)\n", + "Property changes on: \n", + "___________________________________________________________________\n", + "Added: p\n", + "## -0,0 +1 ##\n", + "+v\n", + "\\ No newline at end of property\n", + ] + value = 'v' + + patch_file_path = sbox.get_tempname('my.patch') + svntest.main.file_write(patch_file_path, ''.join(git_patch), 'wb') + + expected_output = wc.State(wc_dir, { + '.' : Item(status=' U'), + }) + expected_disk = svntest.wc.State('', {}) + expected_disk.add({'': Item(props={'p' : value })}) + expected_status = svntest.wc.State(wc_dir, {}) + expected_status.add({'': Item(status=' M', wc_rev='0')}) + expected_skip = wc.State('', { }) + + svntest.actions.run_and_verify_patch(wc_dir, patch_file_path, + expected_output, + expected_disk, + expected_status, + expected_skip, + None, # expected err + True, # check-props + False, # dry-run + ) + + svntest.actions.check_prop('p', wc_dir, [value.encode()]) + +# Test that 'patch' can apply a patch that modifies properties on the root +# of a WC and/or the root of a repository. This test performs a round-trip +# through 'diff --git' and 'patch' rather than assuming any particular +# variant of the patch format. +# +# See dev@ email thread 2018-07-09 from Dmitry Pavlenko, +# '[PATCH] can't "svn patch" working copy root if the patch is in --git format', +# https://lists.apache.org/thread.html/d1d9811ca36fac8cabb9339634840099e22811beac505be2ea59f19f@%3Cdev.subversion.apache.org%3E +@XFail() +def patch_git_wcroot2(sbox): + "patch working copy root" + sbox.build(empty=True) + wc_dir = sbox.wc_dir + + value = 'v' + sbox.simple_propset('p', value, '') + exit_code, git_patch, err_output = svntest.main.run_svn(None, 'diff', + '--git', wc_dir) + + patch_file_path = sbox.get_tempname('my.patch') + svntest.main.file_write(patch_file_path, ''.join(git_patch), 'wb') + + sbox.simple_revert('') + + expected_output = wc.State(wc_dir, { + '.' : Item(status=' U'), + }) + expected_disk = svntest.wc.State('', {}) + expected_disk.add({'': Item(props={'p' : value })}) + expected_status = svntest.wc.State(wc_dir, {}) + expected_status.add({'': Item(status=' M', wc_rev='0')}) + expected_skip = wc.State('', { }) + + svntest.actions.run_and_verify_patch(wc_dir, patch_file_path, + expected_output, + expected_disk, + expected_status, + expected_skip, + None, # expected err + True, # check-props + False, # dry-run + ) + + svntest.actions.check_prop('p', wc_dir, [value.encode()]) + ######################################################################## #Run the tests @@ -7874,6 +8093,10 @@ test_list = [ None, missing_trailing_context, patch_missed_trail, patch_merge, + patch_mergeinfo_in_regular_prop_format, + patch_empty_prop, + patch_git_wcroot, + patch_git_wcroot2, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/prop_tests.py b/subversion/tests/cmdline/prop_tests.py index 3709b20..ada2e09 100755 --- a/subversion/tests/cmdline/prop_tests.py +++ b/subversion/tests/cmdline/prop_tests.py @@ -1768,7 +1768,7 @@ def rm_of_replaced_file(sbox): 'proplist', '-v', mu_path + '@base') expected_output = svntest.verify.UnorderedRegexListOutput([ - 'Properties on', + 'Properties on.*', ' yellow', ' submarine', ' orange', diff --git a/subversion/tests/cmdline/revert_tests.py b/subversion/tests/cmdline/revert_tests.py index 39ce3c6..5bb6870 100755 --- a/subversion/tests/cmdline/revert_tests.py +++ b/subversion/tests/cmdline/revert_tests.py @@ -1550,11 +1550,14 @@ def revert_with_unversioned_targets(sbox): psi_contents = "modified psi\n" # touch delta - open(delta_path, 'w').write(delta_contents) + with open(delta_path, 'w') as f: + f.write(delta_contents) # modify chi psi - open(chi_path, 'w').write(chi_contents) - open(psi_path, 'w').write(psi_contents) + with open(chi_path, 'w') as f: + f.write(chi_contents) + with open(psi_path, 'w') as f: + f.write(psi_contents) # revert expected_output = svntest.verify.UnorderedOutput([ diff --git a/subversion/tests/cmdline/shelf_tests.py b/subversion/tests/cmdline/shelf_tests.py new file mode 100755 index 0000000..899d250 --- /dev/null +++ b/subversion/tests/cmdline/shelf_tests.py @@ -0,0 +1,995 @@ +#!/usr/bin/env python +# +# shelf_tests.py: testing shelving +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import shutil, stat, re, os, logging + +logger = logging.getLogger() + +# Our testing module +import svntest +from svntest import wc +from svntest.verify import make_diff_header, make_no_diff_deleted_header, \ + make_git_diff_header, make_diff_prop_header, \ + make_diff_prop_val, make_diff_prop_deleted, \ + make_diff_prop_added, make_diff_prop_modified + +# (abbreviation) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco +Item = wc.StateItem + +#---------------------------------------------------------------------- + +def state_from_status(wc_dir, + v=True, u=True, q=True): + opts = () + if v: + opts += ('-v',) + if u: + opts += ('-u',) + if q: + opts += ('-q',) + _, output, _ = svntest.main.run_svn(None, 'status', wc_dir, *opts) + return svntest.wc.State.from_status(output, wc_dir) + +def get_wc_state(wc_dir): + """Return a description of the WC state. Include as much info as shelving + should be capable of restoring. + """ + return (state_from_status(wc_dir), + svntest.wc.State.from_wc(wc_dir, load_props=True), + ) + +def check_wc_state(wc_dir, expected): + """Check a description of the WC state. Include as much info as shelving + should be capable of restoring. + """ + expect_st, expect_wc = expected + actual_st, actual_wc = get_wc_state(wc_dir) + + # Verify actual status against expected status. + try: + expect_st.compare_and_display('status', actual_st) + except svntest.tree.SVNTreeError: + svntest.actions._log_tree_state("EXPECT STATUS TREE:", expect_st.old_tree(), + wc_dir) + svntest.actions._log_tree_state("ACTUAL STATUS TREE:", actual_st.old_tree(), + wc_dir) + raise + + # Verify actual WC against expected WC. + try: + expect_wc.compare_and_display('status', actual_wc) + except svntest.tree.SVNTreeError: + svntest.actions._log_tree_state("EXPECT WC TREE:", expect_wc.old_tree(), + wc_dir) + svntest.actions._log_tree_state("ACTUAL WC TREE:", actual_wc.old_tree(), + wc_dir) + raise + +def shelve_unshelve_verify(sbox, modifier, cannot_shelve=False): + """Round-trip: shelve; verify all changes are reverted; + unshelve; verify all changes are restored. + """ + + wc_dir = sbox.wc_dir + virginal_state = get_wc_state(wc_dir) + + # Make some changes to the working copy + modifier(sbox) + + # Save the modified state + modified_state = get_wc_state(wc_dir) + + if cannot_shelve: + svntest.actions.run_and_verify_svn(None, '.* could not be shelved.*', + 'x-shelve', 'foo') + return + + # Shelve; check there are no longer any modifications + svntest.actions.run_and_verify_svn(None, [], + 'x-shelve', 'foo') + check_wc_state(wc_dir, virginal_state) + + # List; ensure the shelf is listed + expected_output = svntest.verify.RegexListOutput( + [r'foo\s*version \d+.*', + r' ', + ]) + svntest.actions.run_and_verify_svn(expected_output, [], 'x-shelves') + + # Unshelve; check the original modifications are here again + svntest.actions.run_and_verify_svn(None, [], + 'x-unshelve', 'foo') + check_wc_state(wc_dir, modified_state) + +#---------------------------------------------------------------------- + +def shelve_unshelve(sbox, modifier, cannot_shelve=False): + """Round-trip: build 'sbox'; apply changes by calling 'modifier(sbox)'; + shelve and unshelve; verify changes are fully reverted and restored. + """ + + if not sbox.is_built(): + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + shelve_unshelve_verify(sbox, modifier, cannot_shelve) + + os.chdir(was_cwd) + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + +def shelve_text_mods(sbox): + "shelve text mods" + + def modifier(sbox): + sbox.simple_append('A/mu', 'appended mu text') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_prop_changes(sbox): + "shelve prop changes" + + def modifier(sbox): + sbox.simple_propset('p', 'v', 'A') + sbox.simple_propset('p', 'v', 'A/mu') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_adds(sbox): + "shelve adds" + + def modifier(sbox): + sbox.simple_add_text('A new file\n', 'A/new') + sbox.simple_add_text('A new file\n', 'A/new2') + sbox.simple_propset('p', 'v', 'A/new2') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +@Issue(4709) +def shelve_deletes(sbox): + "shelve deletes" + + def modifier(sbox): + sbox.simple_rm('A/mu') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_replace(sbox): + "shelve replace" + + def modifier(sbox): + sbox.simple_rm('A/mu') + sbox.simple_add_text('Replacement\n', 'A/mu') + sbox.simple_propset('p', 'v', 'A/mu') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_empty_adds(sbox): + "shelve empty adds" + sbox.build(empty=True) + + def modifier(sbox): + sbox.simple_add_text('', 'empty') + sbox.simple_add_text('', 'empty-with-prop') + sbox.simple_propset('p', 'v', 'empty-with-prop') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_empty_deletes(sbox): + "shelve empty deletes" + sbox.build(empty=True) + sbox.simple_add_text('', 'empty') + sbox.simple_add_text('', 'empty-with-prop') + sbox.simple_propset('p', 'v', 'empty-with-prop') + sbox.simple_commit() + + def modifier(sbox): + sbox.simple_rm('empty', 'empty-with-prop') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_from_inner_path(sbox): + "shelve from inner path" + + def modifier(sbox): + sbox.simple_append('A/mu', 'appended mu text') + + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.ospath('A')) + sbox.wc_dir = '..' + + shelve_unshelve_verify(sbox, modifier) + + os.chdir(was_cwd) + +#---------------------------------------------------------------------- + +def save_revert_restore(sbox, modifier1, modifier2): + "Save 2 checkpoints; revert; restore 1st" + + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + wc_dir = '' + + initial_state = get_wc_state(wc_dir) + + # Make some changes to the working copy + modifier1(sbox) + + # Remember the modified state + modified_state1 = get_wc_state(wc_dir) + + # Save a checkpoint; check nothing changed + svntest.actions.run_and_verify_svn(None, [], + 'x-shelf-save', 'foo') + check_wc_state(wc_dir, modified_state1) + + # Modify again; remember the state; save a checkpoint + modifier2(sbox) + modified_state2 = get_wc_state(wc_dir) + svntest.actions.run_and_verify_svn(None, [], + 'x-shelf-save', 'foo') + check_wc_state(wc_dir, modified_state2) + + # Revert + svntest.actions.run_and_verify_svn(None, [], + 'revert', '-R', '.') + check_wc_state(wc_dir, initial_state) + + # Restore; check the original modifications are here again + svntest.actions.run_and_verify_svn(None, [], + 'x-unshelve', 'foo', '1') + check_wc_state(wc_dir, modified_state1) + + os.chdir(was_cwd) + +#---------------------------------------------------------------------- + +def checkpoint_basic(sbox): + "checkpoint basic" + + def modifier1(sbox): + sbox.simple_append('A/mu', 'appended mu text\n') + + def modifier2(sbox): + sbox.simple_append('iota', 'appended iota text\n') + sbox.simple_append('A/mu', 'appended another line\n') + + save_revert_restore(sbox, modifier1, modifier2) + +#---------------------------------------------------------------------- + +@Issue(3747) +def shelve_mergeinfo(sbox): + "shelve mergeinfo" + + def modifier(sbox): + sbox.simple_propset('svn:mergeinfo', '/trunk/A:1-3,10', 'A') + sbox.simple_propset('svn:mergeinfo', '/trunk/A/mu:1-3,10', 'A/mu') + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def unshelve_refuses_if_conflicts(sbox): + "unshelve refuses if conflicts" + + def modifier1(sbox): + sbox.simple_append('alpha', 'A-mod1\nB\nC\nD\n', truncate=True) + sbox.simple_append('beta', 'A-mod1\nB\nC\nD\n', truncate=True) + + def modifier2(sbox): + sbox.simple_append('beta', 'A-mod2\nB\nC\nD\n', truncate=True) + + sbox.build(empty=True) + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + wc_dir = '' + + sbox.simple_add_text('A\nB\nC\nD\n', 'alpha') + sbox.simple_add_text('A\nB\nC\nD\n', 'beta') + sbox.simple_commit() + initial_state = get_wc_state(wc_dir) + + # Make initial mods; remember this modified state + modifier1(sbox) + modified_state1 = get_wc_state(wc_dir) + assert modified_state1 != initial_state + + # Shelve; check there are no longer any local mods + svntest.actions.run_and_verify_svn(None, [], + 'x-shelve', 'foo') + check_wc_state(wc_dir, initial_state) + + # Make a different local mod that will conflict with the shelf + modifier2(sbox) + modified_state2 = get_wc_state(wc_dir) + + # Try to unshelve; check it fails with an error about a conflict + svntest.actions.run_and_verify_svn(None, '.*[Cc]onflict.*', + 'x-unshelve', 'foo') + # Check nothing changed in the attempt + check_wc_state(wc_dir, modified_state2) + +#---------------------------------------------------------------------- + +def shelve_binary_file_mod(sbox): + "shelve binary file mod" + + sbox.build(empty=True) + + existing_files = ['A/B/existing'] + mod_files = ['bin', 'A/B/bin'] + + sbox.simple_mkdir('A', 'A/B') + for f in existing_files + mod_files: + sbox.simple_add_text('\0\1\2\3\4\5', f) + sbox.simple_commit() + + def modifier(sbox): + for f in mod_files: + sbox.simple_append(f, '\6\5\4\3\2\1\0', truncate=True) + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_binary_file_add(sbox): + "shelve binary file add" + + sbox.build(empty=True) + + existing_files = ['A/B/existing'] + mod_files = ['bin', 'A/B/bin'] + + sbox.simple_mkdir('A', 'A/B') + for f in existing_files: + sbox.simple_add_text('\0\1\2\3\4\5', f) + sbox.simple_commit() + + def modifier(sbox): + for f in mod_files: + sbox.simple_add_text('\0\1\2\3\4\5', f) + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_binary_file_del(sbox): + "shelve binary file del" + + sbox.build(empty=True) + + existing_files = ['A/B/existing'] + mod_files = ['bin', 'A/B/bin'] + + sbox.simple_mkdir('A', 'A/B') + for f in existing_files + mod_files: + sbox.simple_add_text('\0\1\2\3\4\5', f) + sbox.simple_commit() + + def modifier(sbox): + for f in mod_files: + sbox.simple_rm(f) + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_binary_file_replace(sbox): + "shelve binary file replace" + + sbox.build(empty=True) + + existing_files = ['A/B/existing'] + mod_files = ['bin', 'A/B/bin'] + + sbox.simple_mkdir('A', 'A/B') + for f in existing_files + mod_files: + sbox.simple_add_text('\0\1\2\3\4\5', f) + sbox.simple_commit() + + def modifier(sbox): + for f in mod_files: + sbox.simple_rm(f) + sbox.simple_add_text('\6\5\4\3\2\1\0', f) + + shelve_unshelve(sbox, modifier) + +#---------------------------------------------------------------------- + +def shelve_with_log_message(sbox): + "shelve with log message" + + sbox.build(empty=True) + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + sbox.simple_add_text('New file', 'f') + log_message = 'Log message for foo' + svntest.actions.run_and_verify_svn(None, [], + 'x-shelve', 'foo', '-m', log_message) + expected_output = svntest.verify.RegexListOutput( + ['foo .*', + ' ' + log_message + ]) + svntest.actions.run_and_verify_svn(expected_output, [], + 'x-shelf-list') + + os.chdir(was_cwd) + +#---------------------------------------------------------------------- + +def run_and_verify_status(wc_dir_name, status_tree, changelists=[]): + """Run 'status' on WC_DIR_NAME and compare it with the + expected STATUS_TREE. + Returns on success, raises on failure.""" + + if not isinstance(status_tree, wc.State): + raise TypeError('wc.State tree expected') + + cl_opts = ('--cl=' + cl for cl in changelists) + exit_code, output, errput = svntest.main.run_svn(None, 'status', '-q', + wc_dir_name, *cl_opts) + + actual_status = svntest.wc.State.from_status(output, wc_dir=wc_dir_name) + + # Verify actual output against expected output. + try: + status_tree.compare_and_display('status', actual_status) + except svntest.tree.SVNTreeError: + svntest.actions._log_tree_state("ACTUAL STATUS TREE:", actual_status.old_tree(), + wc_dir_name) + raise + +def run_and_verify_shelf_status(wc_dir, expected_status, shelf): + run_and_verify_status(wc_dir, expected_status, + changelists=['svn:shelf:' + shelf]) + +def shelf_status(sbox): + "shelf status" + + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + sbox.simple_add_text('New file', 'f') + sbox.simple_append('iota', 'New text') + sbox.simple_propset('p', 'v', 'A/mu') + sbox.simple_rm('A/B/lambda') + # Not yet supported: + #sbox.simple_rm('A/B/E') + expected_status = state_from_status(sbox.wc_dir, v=False, u=False, q=False) + run_and_verify_status(sbox.wc_dir, expected_status) + + svntest.actions.run_and_verify_svn(None, [], + 'x-shelve', 'foo') + run_and_verify_shelf_status(sbox.wc_dir, expected_status, shelf='foo') + + os.chdir(was_cwd) + +#---------------------------------------------------------------------- + +def shelve_mkdir(sbox): + "shelve mkdir" + + sbox.build() + + def modifier(sbox): + sbox.simple_mkdir('D', 'D/D2') + sbox.simple_propset('p', 'v', 'D', 'D/D2') + + shelve_unshelve(sbox, modifier, cannot_shelve=True) + +#---------------------------------------------------------------------- + +def shelve_rmdir(sbox): + "shelve rmdir" + + sbox.build() + sbox.simple_propset('p', 'v', 'A/C') + sbox.simple_commit() + + def modifier(sbox): + sbox.simple_rm('A/C', 'A/D/G') + + shelve_unshelve(sbox, modifier, cannot_shelve=True) + +#---------------------------------------------------------------------- + +def shelve_replace_dir(sbox): + "shelve replace dir" + + sbox.build() + sbox.simple_propset('p', 'v', 'A/C') + sbox.simple_commit() + + def modifier(sbox): + sbox.simple_rm('A/C', 'A/D/G') + sbox.simple_mkdir('A/C', 'A/C/D2') + + shelve_unshelve(sbox, modifier, cannot_shelve=True) + +#---------------------------------------------------------------------- + +def shelve_file_copy(sbox): + "shelve file copy" + + sbox.build() + + def modifier(sbox): + sbox.simple_copy('iota', 'A/ii') + sbox.simple_propset('p', 'v', 'A/ii') + + shelve_unshelve(sbox, modifier, cannot_shelve=True) + +#---------------------------------------------------------------------- + +def shelve_dir_copy(sbox): + "shelve dir copy" + + sbox.build() + + def modifier(sbox): + sbox.simple_copy('A/B', 'BB') + sbox.simple_propset('p', 'v', 'BB') + + shelve_unshelve(sbox, modifier, cannot_shelve=True) + +#---------------------------------------------------------------------- + +def list_shelves(sbox): + "list_shelves" + + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + # an empty list + svntest.actions.run_and_verify_svn([], [], + 'x-shelf-list', '-q') + + # make two shelves + sbox.simple_append('A/mu', 'appended mu text') + svntest.actions.run_and_verify_svn(None, [], + 'x-shelf-save', 'foo') + sbox.simple_append('A/mu', 'appended more text') + svntest.actions.run_and_verify_svn(None, [], + 'x-shelf-save', 'foo', '-m', 'log msg') + svntest.actions.run_and_verify_svn(None, [], + 'x-shelf-save', 'bar', '-m', 'log msg') + + # We don't check for time-ordering of the shelves. If we want to do so, we + # would need to sleep for timestamps to differ, between creating them. + + # a quiet list + expected_out = svntest.verify.UnorderedRegexListOutput(['foo', 'bar']) + svntest.actions.run_and_verify_svn(expected_out, [], + 'x-shelf-list', '-q') + + # a detailed list + expected_out = svntest.verify.UnorderedRegexListOutput(['foo .* 1 path.*', + ' log msg', + 'bar .* 1 path.*', + ' log msg']) + svntest.actions.run_and_verify_svn(expected_out, [], + 'x-shelf-list') + + os.chdir(was_cwd) + +#---------------------------------------------------------------------- + +def refuse_to_shelve_conflict(sbox): + "refuse to shelve conflict" + + sbox.build(empty=True) + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + # create a tree conflict victim at an unversioned path + sbox.simple_mkdir('topdir') + sbox.simple_commit() + sbox.simple_mkdir('topdir/subdir') + sbox.simple_commit() + sbox.simple_update() + sbox.simple_rm('topdir') + sbox.simple_commit() + sbox.simple_update() + svntest.actions.run_and_verify_svn( + None, [], + 'merge', '-c2', '.', '--ignore-ancestry', '--accept', 'postpone') + svntest.actions.run_and_verify_svn( + None, 'svn: E155015:.*existing.*conflict.*', + 'merge', '-c1', '.', '--ignore-ancestry', '--accept', 'postpone') + + # attempt to shelve + expected_out = svntest.verify.RegexListOutput([ + r'--- .*', + r'--- .*', + r'\? C topdir', + r' > .*', + r' > not shelved']) + svntest.actions.run_and_verify_svn(expected_out, + '.* 1 path could not be shelved', + 'x-shelf-save', 'foo') + + os.chdir(was_cwd) + +#---------------------------------------------------------------------- + +def unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state): + """Run a test scenario in which 'unshelve' needs to merge some shelved + changes made by modifier1() with some committed changes made by + modifier2(). tweak_expected_state() must produce the expected WC state. + """ + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + wc_dir = sbox.wc_dir + + setup(sbox) + sbox.simple_commit() + initial_state = get_wc_state(wc_dir) + + # Make some changes to the working copy + modifier1(sbox) + modified_state = get_wc_state(wc_dir) + + # Shelve; check there are no longer any modifications + svntest.actions.run_and_verify_svn(None, [], + 'x-shelve', 'foo') + check_wc_state(wc_dir, initial_state) + + # Make a different change, with which we shall merge + modifier2(sbox) + sbox.simple_commit() + modified_state[0].tweak('A/mu', wc_rev='3') + + # Unshelve; check the expected result of the merge + svntest.actions.run_and_verify_svn(None, [], + 'x-unshelve', 'foo') + tweak_expected_state(modified_state) + check_wc_state(wc_dir, modified_state) + + os.chdir(was_cwd) + +def unshelve_text_mod_merge(sbox): + "unshelve text mod merge" + + orig_contents='A\nB\nC\nD\nE\n' + mod1_contents='A\nBB\nC\nD\nE\n' + mod2_contents='A\nB\nC\nDD\nE\n' + merged_contents='A\nBB\nC\nDD\nE\n' + + def setup(sbox): + sbox.simple_append('A/mu', orig_contents, truncate=True) + + def modifier1(sbox): + sbox.simple_append('A/mu', mod1_contents, truncate=True) + + def modifier2(sbox): + sbox.simple_append('A/mu', mod2_contents, truncate=True) + + def tweak_expected_state(modified_state): + modified_state[1].tweak('A/mu', contents=merged_contents) + + unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state) + +#---------------------------------------------------------------------- + +def unshelve_text_mod_conflict(sbox): + "unshelve text mod conflict" + + orig_contents='A\nB\nC\nD\nE\n' + mod1_contents='A\nBB\nC\nD\nE\n' + mod2_contents='A\nBCD\nC\nD\nE\n' + merged_contents = 'A\n<<<<<<< .working\nBCD\n||||||| .merge-left\nB\n=======\nBB\n>>>>>>> .merge-right\nC\nD\nE\n' + + def setup(sbox): + sbox.simple_append('A/mu', orig_contents, truncate=True) + + def modifier1(sbox): + sbox.simple_append('A/mu', mod1_contents, truncate=True) + + def modifier2(sbox): + sbox.simple_append('A/mu', mod2_contents, truncate=True) + + def tweak_expected_state(modified_state): + modified_state[0].tweak('A/mu', status='C ') + modified_state[1].tweak('A/mu', contents=merged_contents) + modified_state[1].add({ + 'A/mu.merge-left': Item(contents=orig_contents), + 'A/mu.merge-right': Item(contents=mod1_contents), + 'A/mu.working': Item(contents=mod2_contents), + }) + + unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state) + +#---------------------------------------------------------------------- + +def unshelve_undeclared_binary_mod_conflict(sbox): + "unshelve undeclared binary mod conflict" + + orig_contents='\1\2\3\4\5' + mod1_contents='\1\2\2\3\4\5' + mod2_contents='\1\2\3\4\3\4\5' + merged_contents = '<<<<<<< .working\n' + mod2_contents + '||||||| .merge-left\n' + orig_contents + '=======\n' + mod1_contents + '>>>>>>> .merge-right\n' + + def setup(sbox): + sbox.simple_append('A/mu', orig_contents, truncate=True) + + def modifier1(sbox): + sbox.simple_append('A/mu', mod1_contents, truncate=True) + + def modifier2(sbox): + sbox.simple_append('A/mu', mod2_contents, truncate=True) + + def tweak_expected_state(modified_state): + modified_state[0].tweak('A/mu', status='C ') + modified_state[1].tweak('A/mu', contents=merged_contents) + modified_state[1].add({ + 'A/mu.merge-left': Item(contents=orig_contents), + 'A/mu.merge-right': Item(contents=mod1_contents), + 'A/mu.working': Item(contents=mod2_contents), + }) + + unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state) + +#---------------------------------------------------------------------- + +def unshelve_binary_mod_conflict(sbox): + "unshelve binary mod conflict" + + orig_contents='\1\2\3\4\5' + mod1_contents='\1\2\2\3\4\5' + mod2_contents='\1\2\3\4\3\4\5' + + def setup(sbox): + sbox.simple_append('A/mu', orig_contents, truncate=True) + sbox.simple_propset('svn:mime-type', 'application/octet-stream', 'A/mu') + + def modifier1(sbox): + sbox.simple_append('A/mu', mod1_contents, truncate=True) + + def modifier2(sbox): + sbox.simple_append('A/mu', mod2_contents, truncate=True) + + def tweak_expected_state(modified_state): + modified_state[0].tweak('A/mu', status='C ') + modified_state[1].tweak('A/mu', contents=mod2_contents) + modified_state[1].add({ + 'A/mu.merge-left': Item(contents=orig_contents), + 'A/mu.merge-right': Item(contents=mod1_contents), + }) + + unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state) + +#---------------------------------------------------------------------- + +def unshelve_text_prop_merge(sbox): + "unshelve text prop merge" + + def setup(sbox): + sbox.simple_propset('p1', 'v', 'A/mu') + sbox.simple_propset('p2', 'v', 'A/mu') + + def modifier1(sbox): + sbox.simple_propset('p1', 'changed', 'A/mu') + + def modifier2(sbox): + sbox.simple_propset('p2', 'changed', 'A/mu') + + def tweak_expected_state(wc_state): + wc_state[1].tweak('A/mu', props={'p1':'changed', + 'p2':'changed'}) + + unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state) + +#---------------------------------------------------------------------- + +def unshelve_text_prop_conflict(sbox): + "unshelve text prop conflict" + + orig_contents='A' + mod1_contents='B' + mod2_contents='C' + merged_contents='C' + prej_contents='''Trying to change property 'p' +but the local property value conflicts with the incoming change. +<<<<<<< (local property value) +C||||||| (incoming 'changed from' value) +A======= +B>>>>>>> (incoming 'changed to' value) +''' + + def setup(sbox): + sbox.simple_propset('p', orig_contents, 'A/mu') + + def modifier1(sbox): + sbox.simple_propset('p', mod1_contents, 'A/mu') + + def modifier2(sbox): + sbox.simple_propset('p', mod2_contents, 'A/mu') + + def tweak_expected_state(wc_state): + wc_state[0].tweak('A/mu', status=' C') + wc_state[1].tweak('A/mu', props={'p':merged_contents}) + wc_state[1].add({ + 'A/mu.prej': Item(contents=prej_contents), + }) + + unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state) + +#---------------------------------------------------------------------- + +def run_and_verify_shelf_diff_summarize(output_tree, shelf, *args): + """Run 'svn shelf-diff --summarize' with the arguments *ARGS. + + The subcommand output will be verified against OUTPUT_TREE. Returns + on success, raises on failure. + """ + + if isinstance(output_tree, wc.State): + output_tree = output_tree.old_tree() + + exit_code, output, errput = svntest.actions.run_and_verify_svn( + None, [], + 'x-shelf-diff', '--summarize', shelf, *args) + + actual = svntest.tree.build_tree_from_diff_summarize(output) + + # Verify actual output against expected output. + try: + svntest.tree.compare_trees("output", actual, output_tree) + except svntest.tree.SVNTreeError: + svntest.verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual) + raise + +# Exercise a very basic case of shelf-diff. +def shelf_diff_simple(sbox): + "shelf diff simple" + + sbox.build() + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + wc_dir = sbox.wc_dir + + def setup(sbox): + sbox.simple_propset('p1', 'v', 'A/mu') + sbox.simple_propset('p2', 'v', 'A/mu') + + def modifier1(sbox): + sbox.simple_append('A/mu', 'New line.\n') + sbox.simple_propset('p1', 'changed', 'A/mu') + + setup(sbox) + sbox.simple_commit() + initial_state = get_wc_state(wc_dir) + + # Make some changes to the working copy + modifier1(sbox) + modified_state = get_wc_state(wc_dir) + + svntest.actions.run_and_verify_svn(None, [], + 'x-shelf-save', 'foo') + + # basic svn-style diff + expected_output = make_diff_header('A/mu', 'revision 2', 'working copy') + [ + "@@ -1 +1,2 @@\n", + " This is the file 'mu'.\n", + "+New line.\n", + ] + make_diff_prop_header('A/mu') \ + + make_diff_prop_modified('p1', 'v', 'changed') + svntest.actions.run_and_verify_svn(expected_output, [], + 'x-shelf-diff', 'foo') + + # basic summary diff + expected_diff = svntest.wc.State(wc_dir, { + 'A/mu': Item(status='MM'), + }) + run_and_verify_shelf_diff_summarize(expected_diff, 'foo') + + +######################################################################## +# Run the tests + +# list all tests here, starting with None: +test_list = [ None, + shelve_text_mods, + shelve_prop_changes, + shelve_adds, + shelve_deletes, + shelve_replace, + shelve_empty_adds, + shelve_empty_deletes, + shelve_from_inner_path, + checkpoint_basic, + shelve_mergeinfo, + unshelve_refuses_if_conflicts, + shelve_binary_file_mod, + shelve_binary_file_add, + shelve_binary_file_del, + shelve_binary_file_replace, + shelve_with_log_message, + shelf_status, + shelve_mkdir, + shelve_rmdir, + shelve_replace_dir, + shelve_file_copy, + shelve_dir_copy, + list_shelves, + refuse_to_shelve_conflict, + unshelve_text_mod_merge, + unshelve_text_mod_conflict, + unshelve_undeclared_binary_mod_conflict, + unshelve_binary_mod_conflict, + unshelve_text_prop_merge, + unshelve_text_prop_conflict, + shelf_diff_simple, + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. diff --git a/subversion/tests/cmdline/shelve_tests.py b/subversion/tests/cmdline/shelve_tests.py deleted file mode 100755 index a71ddbb..0000000 --- a/subversion/tests/cmdline/shelve_tests.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env python -# -# shelve_tests.py: testing shelving -# -# Subversion is a tool for revision control. -# See http://subversion.apache.org for more information. -# -# ==================================================================== -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -###################################################################### - -# General modules -import shutil, stat, re, os, logging - -logger = logging.getLogger() - -# Our testing module -import svntest -from svntest import wc - -# (abbreviation) -Skip = svntest.testcase.Skip_deco -SkipUnless = svntest.testcase.SkipUnless_deco -XFail = svntest.testcase.XFail_deco -Issues = svntest.testcase.Issues_deco -Issue = svntest.testcase.Issue_deco -Wimp = svntest.testcase.Wimp_deco -Item = wc.StateItem - -#---------------------------------------------------------------------- - -def shelve_unshelve_verify(sbox): - """Round-trip: shelve; verify all changes are reverted; - unshelve; verify all changes are restored. - """ - - wc_dir = sbox.wc_dir - - # Save the modified state - _, output, _ = svntest.main.run_svn(None, 'status', '-v', '-u', '-q', - wc_dir) - modified_state = svntest.wc.State.from_status(output, wc_dir) - - # Shelve; check there are no longer any modifications - svntest.actions.run_and_verify_svn(None, [], - 'shelve', 'foo') - virginal_state = svntest.actions.get_virginal_state(wc_dir, 1) - svntest.actions.run_and_verify_status(wc_dir, virginal_state) - - # Unshelve; check the original modifications are here again - svntest.actions.run_and_verify_svn(None, [], - 'unshelve', 'foo') - svntest.actions.run_and_verify_status(wc_dir, modified_state) - -#---------------------------------------------------------------------- - -def shelve_unshelve(sbox, modifier): - """Round-trip: build 'sbox'; apply changes by calling 'modifier(sbox)'; - shelve and unshelve; verify changes are fully reverted and restored. - """ - - sbox.build() - was_cwd = os.getcwd() - os.chdir(sbox.wc_dir) - sbox.wc_dir = '' - - # Make some changes to the working copy - modifier(sbox) - - shelve_unshelve_verify(sbox) - - os.chdir(was_cwd) - -###################################################################### -# Tests -# -# Each test must return on success or raise on failure. - -def shelve_text_mods(sbox): - "shelve text mods" - - def modifier(sbox): - sbox.simple_append('A/mu', 'appended mu text') - - shelve_unshelve(sbox, modifier) - -#---------------------------------------------------------------------- - -def shelve_prop_changes(sbox): - "shelve prop changes" - - def modifier(sbox): - sbox.simple_propset('p', 'v', 'A') - sbox.simple_propset('p', 'v', 'A/mu') - - shelve_unshelve(sbox, modifier) - -#---------------------------------------------------------------------- - -def shelve_adds(sbox): - "shelve adds" - - def modifier(sbox): - sbox.simple_append('A/new', 'A new file\n') - sbox.simple_add('A/new') - sbox.simple_append('A/new2', 'A new file\n') - sbox.simple_add('A/new2') - sbox.simple_propset('p', 'v', 'A/new2') - - shelve_unshelve(sbox, modifier) - -#---------------------------------------------------------------------- - -@XFail() -@Issue(4709) -def shelve_deletes(sbox): - "shelve deletes" - - def modifier(sbox): - sbox.simple_rm('A/mu') - - shelve_unshelve(sbox, modifier) - -#---------------------------------------------------------------------- - -def shelve_from_inner_path(sbox): - "shelve from inner path" - - def modifier(sbox): - sbox.simple_append('A/mu', 'appended mu text') - - sbox.build() - was_cwd = os.getcwd() - os.chdir(sbox.ospath('A')) - sbox.wc_dir = '..' - - modifier(sbox) - shelve_unshelve_verify(sbox) - - os.chdir(was_cwd) - -#---------------------------------------------------------------------- - -######################################################################## -# Run the tests - -# list all tests here, starting with None: -test_list = [ None, - shelve_text_mods, - shelve_prop_changes, - shelve_adds, - shelve_deletes, - shelve_from_inner_path, - ] - -if __name__ == '__main__': - svntest.main.run_tests(test_list) - # NOTREACHED - - -### End of file. diff --git a/subversion/tests/cmdline/special_tests.py b/subversion/tests/cmdline/special_tests.py index db612c1..2ec22cb 100755 --- a/subversion/tests/cmdline/special_tests.py +++ b/subversion/tests/cmdline/special_tests.py @@ -535,7 +535,7 @@ def diff_symlink_to_dir(sbox): "+++ link\t(working copy)\n", "@@ -0,0 +1 @@\n", "+link A/D\n", - "\ No newline at end of file\n", + "\\ No newline at end of file\n", "\n", "Property changes on: link\n", "___________________________________________________________________\n", @@ -730,7 +730,8 @@ def unrelated_changed_special_status(sbox): os.chdir(os.path.join(sbox.wc_dir, 'A/D/H')) - open('chi', 'a').write('random local mod') + with open('chi', 'a') as f: + f.write('random local mod') os.unlink('psi') os.symlink('omega', 'psi') # omega is versioned! svntest.main.run_svn(None, 'changelist', 'chi cl', 'chi') diff --git a/subversion/tests/cmdline/stat_tests.py b/subversion/tests/cmdline/stat_tests.py index afab961..e0f1306 100755 --- a/subversion/tests/cmdline/stat_tests.py +++ b/subversion/tests/cmdline/stat_tests.py @@ -1949,7 +1949,8 @@ def modified_modulo_translation(sbox): sbox.simple_commit() # CRLF it. - open(sbox.ospath('iota'), 'wb').write("This is the file 'iota'.\r\n") + with open(sbox.ospath('iota'), 'wb') as f: + f.write("This is the file 'iota'.\r\n") # Run status. Expect some output. # TODO: decide how such files should show in the output; whether they diff --git a/subversion/tests/cmdline/svnadmin_tests.py b/subversion/tests/cmdline/svnadmin_tests.py index 40b5e97..4d5303c 100755 --- a/subversion/tests/cmdline/svnadmin_tests.py +++ b/subversion/tests/cmdline/svnadmin_tests.py @@ -53,6 +53,23 @@ Wimp = svntest.testcase.Wimp_deco SkipDumpLoadCrossCheck = svntest.testcase.SkipDumpLoadCrossCheck_deco Item = svntest.wc.StateItem +def read_rep_cache(repo_dir): + """Return the rep-cache contents as a dict {hash: (rev, index, ...)}. + """ + db_path = os.path.join(repo_dir, 'db', 'rep-cache.db') + db1 = svntest.sqlite3.connect(db_path) + schema1 = db1.execute("pragma user_version").fetchone()[0] + # Can't test newer rep-cache schemas with an old built-in SQLite. + if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2): + raise svntest.Failure("Can't read rep-cache schema %d using old " + "Python-SQLite version %s < (3,8,2)" % + (schema1, + svntest.sqlite3.sqlite_version_info)) + + content = { row[0]: row[1:] for row in + db1.execute("select * from rep_cache") } + return content + def check_hotcopy_bdb(src, dst): "Verify that the SRC BDB repository has been correctly copied to DST." ### TODO: This function should be extended to verify all hotcopied files, @@ -256,7 +273,8 @@ def patch_format(repo_dir, shard_size): new_contents = b"\n".join(processed_lines) os.chmod(format_path, svntest.main.S_ALL_RW) - open(format_path, 'wb').write(new_contents) + with open(format_path, 'wb') as f: + f.write(new_contents) def is_sharded(repo_dir): """Return whether the FSFS repository REPO_DIR is sharded.""" @@ -777,9 +795,13 @@ def verify_windows_paths_in_repos(sbox): def fsfs_file(repo_dir, kind, rev): if svntest.main.options.server_minor_version >= 5: if svntest.main.options.fsfs_sharding is None: + if svntest.main.is_fs_type_fsx(): + rev = 'r' + rev return os.path.join(repo_dir, 'db', kind, '0', rev) else: shard = int(rev) // svntest.main.options.fsfs_sharding + if svntest.main.is_fs_type_fsx(): + rev = 'r' + rev path = os.path.join(repo_dir, 'db', kind, str(shard), rev) if svntest.main.options.fsfs_packing is None or kind == 'revprops': @@ -1043,7 +1065,7 @@ def fsfs_recover_old_db_current(sbox): def load_with_parent_dir(sbox): "'svnadmin load --parent-dir' reparents mergeinfo" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2983. ## + ## See https://issues.apache.org/jira/browse/SVN-2983. ## sbox.build(empty=True) dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), @@ -1134,7 +1156,7 @@ def set_uuid(sbox): def reflect_dropped_renumbered_revs(sbox): "reflect dropped renumbered revs in svn:mergeinfo" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3020. ## + ## See https://issues.apache.org/jira/browse/SVN-3020. ## sbox.build(empty=True) @@ -1326,7 +1348,7 @@ def verify_with_invalid_revprops(sbox): # 2) Dump 'SOURCE-REPOS' in a series of incremental dumps and load # each of them to 'TARGET-REPOS'. # -# See http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc13 +# See https://issues.apache.org/jira/browse/SVN-3020#desc13 @Issue(3020) def dont_drop_valid_mergeinfo_during_incremental_loads(sbox): "don't filter mergeinfo revs from incremental dump" @@ -1512,7 +1534,7 @@ def dont_drop_valid_mergeinfo_during_incremental_loads(sbox): # Check the resulting mergeinfo. We expect the exact same results # as Part 3. - # See http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16. + # See https://issues.apache.org/jira/browse/SVN-3020#desc16. svntest.actions.run_and_verify_svn(expected_output, [], 'propget', 'svn:mergeinfo', '-R', sbox.repo_url) @@ -1523,7 +1545,7 @@ def dont_drop_valid_mergeinfo_during_incremental_loads(sbox): def hotcopy_symlink(sbox): "'svnadmin hotcopy' replicates symlink" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2591. ## + ## See https://issues.apache.org/jira/browse/SVN-2591. ## # Create a repository. sbox.build(create_wc=False, empty=True) @@ -1629,9 +1651,9 @@ text sbox.build(empty=True) # Try to load the dumpstream, expecting a failure (because of mixed EOLs). - exp_err = svntest.verify.RegexListOutput(['svnadmin: E125005', - 'svnadmin: E125005', - 'svnadmin: E125017'], + exp_err = svntest.verify.RegexListOutput(['svnadmin: E125005:.*', + 'svnadmin: E125005:.*', + 'svnadmin: E125017:.*'], match_all=False) load_and_verify_dumpstream(sbox, [], exp_err, dumpfile_revisions, False, dump_str, '--ignore-uuid') @@ -1760,10 +1782,10 @@ def test_lslocks_and_rmlocks(sbox): def expected_output_list(path): return [ "Path: " + path, - "UUID Token: opaquelocktoken", + "UUID Token: opaquelocktoken:.*", "Owner: jrandom", - "Created:", - "Expires:", + "Created:.*", + "Expires:.*", "Comment \(1 line\):", "Locking files", "\n", # empty line @@ -1814,7 +1836,7 @@ def test_lslocks_and_rmlocks(sbox): def load_ranges(sbox): "'svnadmin load --revision X:Y'" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3734. ## + ## See https://issues.apache.org/jira/browse/SVN-3734. ## sbox.build(empty=True) dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), @@ -2165,7 +2187,7 @@ def verify_keep_going(sbox): sbox.repo_dir) if (svntest.main.is_fs_log_addressing()): - exp_out = svntest.verify.RegexListOutput([".*Verifying metadata at revision 0"]) + exp_out = svntest.verify.RegexListOutput([".*Verifying metadata at revision 0.*"]) else: exp_out = svntest.verify.RegexListOutput([".*Verified revision 0.", ".*Verified revision 1."]) @@ -2856,10 +2878,7 @@ def verify_quickly(sbox): "verify quickly using metadata" sbox.build(create_wc = False) - if svntest.main.is_fs_type_fsfs(): - rev_file = open(fsfs_file(sbox.repo_dir, 'revs', '1'), 'r+b') - else: - rev_file = open(fsfs_file(sbox.repo_dir, 'revs', 'r1'), 'r+b') + rev_file = open(fsfs_file(sbox.repo_dir, 'revs', '1'), 'r+b') # set new contents rev_file.seek(8) @@ -3458,7 +3477,8 @@ def load_from_file(sbox): sbox.build(empty=True) file = sbox.get_tempname() - open(file, 'wb').writelines(clean_dumpfile()) + with open(file, 'wb') as f: + f.writelines(clean_dumpfile()) svntest.actions.run_and_verify_svnadmin2(None, [], 0, 'load', '--file', file, '--ignore-uuid', sbox.repo_dir) @@ -3762,7 +3782,7 @@ def dump_exclude_all_rev_changes(sbox): # Check log. Revision properties ('svn:log' etc.) should be empty for r2. expected_output = svntest.verify.RegexListOutput([ '-+\\n', - 'r3\ |\ jrandom\ |\ .*\ |\ 1\ line\\n', + 'r3 | jrandom | .* | 1 line\\n', re.escape('Changed paths:'), re.escape(' A /r3a'), re.escape(' A /r3b'), @@ -3774,7 +3794,7 @@ def dump_exclude_all_rev_changes(sbox): '', '', '-+\\n', - 'r1\ |\ jrandom\ |\ .*\ |\ 1\ line\\n', + 'r1 | jrandom | .* | 1 line\\n', re.escape('Changed paths:'), re.escape(' A /r1a'), re.escape(' A /r1b'), @@ -3823,6 +3843,76 @@ def load_issue4725(sbox): sbox2.build(create_wc=False, empty=True) load_and_verify_dumpstream(sbox2, None, [], None, False, dump, '-M100') +@Issue(4767) +def dump_no_canonicalize_svndate(sbox): + "svnadmin dump shouldn't canonicalize svn:date" + + sbox.build(create_wc=False, empty=True) + svntest.actions.enable_revprop_changes(sbox.repo_dir) + + # set svn:date in a non-canonical format (not six decimal places) + propval = "2015-01-01T00:00:00.0Z" + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + "propset", "--revprop", "-r0", "svn:date", + propval, + sbox.repo_url) + + dump_lines = svntest.actions.run_and_verify_dump(sbox.repo_dir) + assert propval + '\n' in dump_lines + +def check_recover_prunes_rep_cache(sbox, enable_rep_sharing): + """Check 'recover' prunes the rep-cache while enable-rep-sharing is + true/false. + """ + # Remember the initial rep cache content. + rep_cache_r1 = read_rep_cache(sbox.repo_dir) + #print '\n'.join([h + ": " + repr(ref) for h, ref in rep_cache_r1.items()]) + + # Commit one new rep and check the rep-cache is extended. + sbox.simple_append('iota', 'New line.\n') + sbox.simple_commit() + rep_cache_r2 = read_rep_cache(sbox.repo_dir) + assert len(rep_cache_r2) == len(rep_cache_r1) + 1 + + # To test 'recover' while rep-sharing is disabled, disable it now. + if not enable_rep_sharing: + fsfs_conf = svntest.main.get_fsfs_conf_file_path(sbox.repo_dir) + svntest.main.file_append(fsfs_conf, + "[rep-sharing]\n" + "enable-rep-sharing = false\n") + + # Break r2 in such a way that 'recover' will discard it + head_rev_path = fsfs_file(sbox.repo_dir, 'revs', '2') + os.remove(head_rev_path) + current_path = os.path.join(sbox.repo_dir, 'db', 'current') + svntest.main.file_write(current_path, '1\n') + + # Recover back to r1. + svntest.actions.run_and_verify_svnadmin(None, [], + "recover", sbox.repo_dir) + + # Check the rep-cache is pruned. + rep_cache_recovered = read_rep_cache(sbox.repo_dir) + assert rep_cache_recovered == rep_cache_r1 + +@Issue(4077) +@SkipUnless(svntest.main.is_fs_type_fsfs) +@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid) +def recover_prunes_rep_cache_when_enabled(sbox): + "recover prunes rep cache when enabled" + sbox.build() + + check_recover_prunes_rep_cache(sbox, enable_rep_sharing=True) + +@Issue(4077) +@SkipUnless(svntest.main.is_fs_type_fsfs) +@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid) +def recover_prunes_rep_cache_when_disabled(sbox): + "recover prunes rep cache when disabled" + sbox.build() + + check_recover_prunes_rep_cache(sbox, enable_rep_sharing=False) + ######################################################################## # Run the tests @@ -3897,6 +3987,9 @@ test_list = [ None, dump_exclude_all_rev_changes, dump_invalid_filtering_option, load_issue4725, + dump_no_canonicalize_svndate, + recover_prunes_rep_cache_when_enabled, + recover_prunes_rep_cache_when_disabled, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/svnauthz_tests.py b/subversion/tests/cmdline/svnauthz_tests.py index fd1de40..ac6a375 100755 --- a/subversion/tests/cmdline/svnauthz_tests.py +++ b/subversion/tests/cmdline/svnauthz_tests.py @@ -197,9 +197,8 @@ def svnauthz_validate_txn_test(sbox): svntest.main.create_python_hook_script(pre_commit_hook, hook_instance) svntest.main.file_append(authz_path, 'x') expected_status.tweak('A/authz', status=' ', wc_rev=4) - if svntest.actions.run_and_verify_commit(wc_dir, expected_output, - expected_status): - raise svntest.Failure + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) expected_data = svntest.verify.ExpectedOutput("Exit 2\n", match_all=False) verify_logfile(logfilepath, expected_data) @@ -275,9 +274,8 @@ def svnauthz_accessof_repo_test(sbox): expected_status.add({ 'A/authz' : Item(status=' ', wc_rev=2), }) - if svntest.actions.run_and_verify_commit(wc_dir, expected_output, - expected_status): - raise svntest.Failure + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) # Anonymous access with no path, and no repository should be rw # since it returns the highest level of access granted anywhere. diff --git a/subversion/tests/cmdline/svndumpfilter_tests.py b/subversion/tests/cmdline/svndumpfilter_tests.py index 7ee09a4..a45b50b 100755 --- a/subversion/tests/cmdline/svndumpfilter_tests.py +++ b/subversion/tests/cmdline/svndumpfilter_tests.py @@ -83,7 +83,7 @@ def filter_and_return_output(dump, bufsize=0, *varargs): def reflect_dropped_renumbered_revs(sbox): "reflect dropped renumbered revs in svn:mergeinfo" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2982. ## + ## See https://issues.apache.org/jira/browse/SVN-2982. ## # Test svndumpfilter with include option sbox.build(empty=True) @@ -134,7 +134,7 @@ def svndumpfilter_loses_mergeinfo(sbox): "svndumpfilter loses mergeinfo" #svndumpfilter loses mergeinfo if invoked without --renumber-revs - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=3181. ## + ## See https://issues.apache.org/jira/browse/SVN-3181. ## sbox.build(empty=True) dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]), @@ -217,7 +217,7 @@ def _simple_dumpfilter_test(sbox, dumpfile, *dumpargs): @Issue(2697) def dumpfilter_with_targets(sbox): "svndumpfilter --targets blah" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=2697. ## + ## See https://issues.apache.org/jira/browse/SVN-2697. ## sbox.build(empty=True) @@ -677,7 +677,7 @@ def accepts_deltas(sbox): @Issue(4234) def dumpfilter_targets_expect_leading_slash_prefixes(sbox): "dumpfilter targets expect leading '/' in prefixes" - ## See http://subversion.tigris.org/issues/show_bug.cgi?id=4234. ## + ## See https://issues.apache.org/jira/browse/SVN-4234. ## sbox.build(empty=True) diff --git a/subversion/tests/cmdline/svnfsfs_tests.py b/subversion/tests/cmdline/svnfsfs_tests.py index e1fd73d..aff3c2f 100755 --- a/subversion/tests/cmdline/svnfsfs_tests.py +++ b/subversion/tests/cmdline/svnfsfs_tests.py @@ -94,7 +94,8 @@ def patch_format(repo_dir, shard_size): new_contents = b"\n".join(processed_lines) os.chmod(format_path, svntest.main.S_ALL_RW) - open(format_path, 'wb').write(new_contents) + with open(format_path, 'wb') as f: + f.write(new_contents) ###################################################################### # Tests diff --git a/subversion/tests/cmdline/svnmover_tests.py b/subversion/tests/cmdline/svnmover_tests.py index bfdbb1f..6c98b64 100755 --- a/subversion/tests/cmdline/svnmover_tests.py +++ b/subversion/tests/cmdline/svnmover_tests.py @@ -469,7 +469,7 @@ rm A/B/C/Y ' D /top0/A/B/C/Y', ])) expected_output = svntest.verify.UnorderedRegexListOutput(escaped - + ['^-', '^r2', '^-', '^Changed paths:',]) + + ['^--*', '^r2.*', '^--*', '^Changed paths:',]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-qvr2', repo_url) @@ -755,7 +755,7 @@ def simple_moves_within_a_branch(sbox): 'mv lib/foo/y2 y2') # move and rename, dir with children test_svnmover2(sbox, '/trunk', - reported_br_diff('') + + reported_br_diff('trunk') + reported_add('subdir') + reported_move('lib', 'subdir/lib2'), 'mkdir subdir', @@ -765,7 +765,7 @@ def simple_moves_within_a_branch(sbox): # moves and renames together # (put it all back to how it was, in one commit) test_svnmover2(sbox, '/trunk', - reported_br_diff('') + + reported_br_diff('trunk') + reported_move('subdir/lib2/README.txt', 'README') + reported_move('subdir/lib2', 'lib') + reported_move('y2', 'lib/foo/y') + diff --git a/subversion/tests/cmdline/svnmucc_tests.py b/subversion/tests/cmdline/svnmucc_tests.py index f95c558..3159ec2 100755 --- a/subversion/tests/cmdline/svnmucc_tests.py +++ b/subversion/tests/cmdline/svnmucc_tests.py @@ -458,7 +458,7 @@ rm A/B/C/Y ' D /A/B/C/Y', ])) expected_output = svntest.verify.UnorderedRegexListOutput(excaped - + ['^-', '^r3', '^-', '^Changed paths:',]) + + ['^--*', '^r3.*', '^--*', '^Changed paths:',]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-qvr3', repo_url) diff --git a/subversion/tests/cmdline/svnrdump_tests.py b/subversion/tests/cmdline/svnrdump_tests.py index ae6a7e0..b6d4c6f 100755 --- a/subversion/tests/cmdline/svnrdump_tests.py +++ b/subversion/tests/cmdline/svnrdump_tests.py @@ -80,7 +80,7 @@ def compare_repos_dumps(sbox, other_dumpfile, ### This call kind-of assumes EXPECTED is first and ACTUAL is second. svntest.verify.compare_dump_files( - "Dump files", "DUMP", other_dumpfile, sbox_dumpfile) + None, None, other_dumpfile, sbox_dumpfile) def run_dump_test(sbox, dumpfile_name, expected_dumpfile_name = None, subdir = None, bypass_prop_validation = False, @@ -455,7 +455,7 @@ def reflect_dropped_renumbered_revs(sbox): # 2) Dump 'SOURCE-REPOS' in a series of incremental dumps and load # each of them to 'TARGET-REPOS'. # -# See http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc13 +# See https://issues.apache.org/jira/browse/SVN-3020#desc13 # # This test replicates svnadmin_tests.py 20 'don't filter mergeinfo revs # from incremental dump' but uses 'svnrdump [dump|load]' in place of @@ -704,7 +704,7 @@ def dont_drop_valid_mergeinfo_during_incremental_svnrdump_loads(sbox): # Check the resulting mergeinfo. We expect the exact same results # as Part 3. - # See http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16. + # See https://issues.apache.org/jira/browse/SVN-3020#desc16. svntest.actions.run_and_verify_svn(expected_output, [], 'propget', 'svn:mergeinfo', '-R', sbox.repo_url) diff --git a/subversion/tests/cmdline/svnsync_authz_tests.py b/subversion/tests/cmdline/svnsync_authz_tests.py index e8b9444..e464cde 100755 --- a/subversion/tests/cmdline/svnsync_authz_tests.py +++ b/subversion/tests/cmdline/svnsync_authz_tests.py @@ -415,7 +415,7 @@ def specific_deny_authz(sbox): # For mod_dav_svn's parent path setup we need per-repos permissions in # the authz file... - if sbox.repo_url.startswith('http'): + if svntest.main.is_ra_type_dav(): src_authz = sbox.authz_name() dst_authz = dest_sbox.authz_name() write_authz_file(sbox, None, diff --git a/subversion/tests/cmdline/svnsync_tests.py b/subversion/tests/cmdline/svnsync_tests.py index ba55fb5..744fbbb 100755 --- a/subversion/tests/cmdline/svnsync_tests.py +++ b/subversion/tests/cmdline/svnsync_tests.py @@ -167,7 +167,7 @@ def verify_mirror(dest_sbox, exp_dump_file_contents): dest_dump = svntest.actions.run_and_verify_dump(dest_sbox.repo_dir) svntest.verify.compare_dump_files( - "Dump files", "DUMP", exp_dump_file_contents, dest_dump) + None, None, exp_dump_file_contents, dest_dump) def run_test(sbox, dump_file_name, subdir=None, exp_dump_file_name=None, bypass_prop_validation=False, source_prop_encoding=None, diff --git a/subversion/tests/cmdline/svntest/actions.py b/subversion/tests/cmdline/svntest/actions.py index 8930b63..1051844 100644 --- a/subversion/tests/cmdline/svntest/actions.py +++ b/subversion/tests/cmdline/svntest/actions.py @@ -74,6 +74,11 @@ def setup_pristine_greek_repository(): if not os.path.exists(main.general_repo_dir): os.makedirs(main.general_repo_dir) # this also creates all the intermediate dirs + if not os.path.exists(main.other_dav_root_dir): + os.makedirs(main.other_dav_root_dir) + if not os.path.exists(main.non_dav_root_dir): + os.makedirs(main.non_dav_root_dir) + # If there's no pristine repos, create one. if not os.path.exists(main.pristine_greek_repos_dir): if main.options.fsfs_version is not None: diff --git a/subversion/tests/cmdline/svntest/main.py b/subversion/tests/cmdline/svntest/main.py index 09a9722..4aa6041 100644 --- a/subversion/tests/cmdline/svntest/main.py +++ b/subversion/tests/cmdline/svntest/main.py @@ -56,7 +56,7 @@ import svntest from svntest import Failure from svntest import Skip -SVN_VER_MINOR = 10 +SVN_VER_MINOR = 11 ###################################################################### # @@ -224,6 +224,10 @@ SVN_PROP_INHERITABLE_IGNORES = "svn:global-ignores" general_repo_dir = os.path.join(work_dir, "repositories") general_wc_dir = os.path.join(work_dir, "working_copies") +# Directories used for DAV tests +other_dav_root_dir = os.path.join(work_dir, "fsdavroot") +non_dav_root_dir = os.path.join(work_dir, "nodavroot") + # temp directory in which we will create our 'pristine' local # repository and other scratch data. This should be removed when we # quit and when we startup. @@ -978,7 +982,8 @@ def file_write(path, contents, mode='w'): which is (w)rite by default.""" if sys.version_info < (3, 0): - open(path, mode).write(contents) + with open(path, mode) as f: + f.write(contents) else: # Python 3: Write data in the format required by MODE, i.e. byte arrays # to 'b' files, utf-8 otherwise.""" @@ -990,9 +995,11 @@ def file_write(path, contents, mode='w'): contents = contents.decode("utf-8") if isinstance(contents, str): - codecs.open(path, mode, "utf-8").write(contents) + with codecs.open(path, mode, "utf-8") as f: + f.write(contents) else: - open(path, mode).write(contents) + with open(path, mode) as f: + f.write(contents) # For making local mods to files def file_append(path, new_text): @@ -1008,7 +1015,8 @@ def file_append_binary(path, new_text): def file_substitute(path, contents, new_contents): """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS""" fcontent = open(path, 'r').read().replace(contents, new_contents) - open(path, 'w').write(fcontent) + with open(path, 'w') as f: + f.write(fcontent) # For setting up authz, hooks and making other tweaks to created repos def _post_create_repos(path, minor_version = None): @@ -1035,7 +1043,8 @@ def _post_create_repos(path, minor_version = None): users += (crosscheck_username + " = " + crosscheck_password + "\n") file_append(os.path.join(path, "conf", "passwd"), users) - if options.fs_type is None or options.fs_type == 'fsfs': + if options.fs_type is None or options.fs_type == 'fsfs' or \ + options.fs_type == 'fsx': # fsfs.conf file if (minor_version is None or minor_version >= 6): confpath = get_fsfs_conf_file_path(path) @@ -1633,6 +1642,15 @@ def server_has_atomic_revprop(): def server_has_reverse_get_file_revs(): return options.server_caps.has_reverse_get_file_revs +def python_sqlite_can_read_our_wc_db(): + """Check if the Python builtin is capable enough to peek into wc.db""" + # Currently enough (1.7-1.9) + return svntest.sqlite3.sqlite_version_info >= (3, 6, 18) + +def python_sqlite_can_read_without_rowid(): + """Check if the Python builtin is capable enough to read new rep-cache""" + return svntest.sqlite3.sqlite_version_info >= (3, 8, 2) + def is_plaintext_password_storage_disabled(): try: predicate = re.compile("^WARNING: Plaintext password storage is enabled!") @@ -2335,6 +2353,8 @@ def execute_tests(test_list, serial_only = False, test_name = None, global pristine_url global pristine_greek_repos_url + global other_dav_root_url + global non_dav_root_url global svn_binary global svnadmin_binary global svnlook_binary @@ -2414,6 +2434,10 @@ def execute_tests(test_list, serial_only = False, test_name = None, pristine_greek_repos_dir.replace( os.path.sep, '/')) + other_dav_root_url = options.test_area_url + '/fsdavroot' + non_dav_root_url = options.test_area_url + '/nodavroot' + + if options.use_jsvn: if options.svn_bin is None: options.svn_bin = '' diff --git a/subversion/tests/cmdline/svntest/mergetrees.py b/subversion/tests/cmdline/svntest/mergetrees.py index 0cee3d2..0cee3d2 100755..100644 --- a/subversion/tests/cmdline/svntest/mergetrees.py +++ b/subversion/tests/cmdline/svntest/mergetrees.py diff --git a/subversion/tests/cmdline/svntest/sandbox.py b/subversion/tests/cmdline/svntest/sandbox.py index b1c9861..cc8df2e 100644 --- a/subversion/tests/cmdline/svntest/sandbox.py +++ b/subversion/tests/cmdline/svntest/sandbox.py @@ -168,7 +168,8 @@ class Sandbox: or open(self.authz_file,'r').read() != default_authz)): tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name) - open(tmp_authz_file, 'w').write(default_authz) + with open(tmp_authz_file, 'w') as f: + f.write(default_authz) shutil.move(tmp_authz_file, self.authz_file) def authz_name(self, repo_dir=None): @@ -492,7 +493,8 @@ class Sandbox: if not svnrdump_headers_always.match(l)] # Ignore differences in number of blank lines between node records, # as svnrdump puts 3 whereas svnadmin puts 2 after a replace-with-copy. - svntest.verify.compare_dump_files(None, None, + svntest.verify.compare_dump_files('svnadmin dump, tweaked', + 'svnrdump dump, tweaked', dumpfile_a_d_cmp, dumpfile_r_d_cmp, ignore_number_of_blank_lines=True) @@ -523,20 +525,22 @@ class Sandbox: reloaded_dumpfile_a_n = svntest.actions.run_and_verify_dump(repo_dir_a_n) reloaded_dumpfile_a_d = svntest.actions.run_and_verify_dump(repo_dir_a_d) reloaded_dumpfile_r_d = svntest.actions.run_and_verify_dump(repo_dir_r_d) - svntest.verify.compare_dump_files(None, None, + svntest.verify.compare_dump_files('svnadmin dump no delta, loaded, dumped', + 'svnadmin dump --deltas, loaded, dumped', reloaded_dumpfile_a_n, reloaded_dumpfile_a_d, ignore_uuid=True) - svntest.verify.compare_dump_files(None, None, + svntest.verify.compare_dump_files('svnadmin dump, loaded, dumped', + 'svnrdump dump, loaded, dumped', reloaded_dumpfile_a_d, reloaded_dumpfile_r_d, ignore_uuid=True) # Run each dump through svndumpfilter and check for no further change. - for dumpfile in [dumpfile_a_n, - dumpfile_a_d, - dumpfile_r_d - ]: + for dumpfile, dumpfile_desc in [(dumpfile_a_n, 'svnadmin dump'), + (dumpfile_a_d, 'svnadmin dump --deltas'), + (dumpfile_r_d, 'svnrdump dump'), + ]: ### No buffer size seems to work for update_tests-2. So skip that test? ### (Its dumpfile size is ~360 KB non-delta, ~180 KB delta.) if len(''.join(dumpfile)) > 100000: @@ -550,7 +554,9 @@ class Sandbox: # svndumpfilter strips them. # Ignore differences in number of blank lines between node records, # as svndumpfilter puts 3 instead of 2 after an add or delete record. - svntest.verify.compare_dump_files(None, None, dumpfile, dumpfile2, + svntest.verify.compare_dump_files(dumpfile_desc, + 'after svndumpfilter include /', + dumpfile, dumpfile2, expect_content_length_always=True, ignore_empty_prop_sections=True, ignore_number_of_blank_lines=True) diff --git a/subversion/tests/cmdline/svntest/tree.py b/subversion/tests/cmdline/svntest/tree.py index 6c34238..fbcbcfc 100644 --- a/subversion/tests/cmdline/svntest/tree.py +++ b/subversion/tests/cmdline/svntest/tree.py @@ -267,19 +267,8 @@ class SVNTreeNode: line += "%-20s: Item(" % ("'%s'" % path.replace(os.sep, '/')) comma = False - mime_type = self.props.get("svn:mime-type") - if not mime_type or mime_type.startswith("text/"): - if self.contents is not None: - # Escape some characters for nicer script and readability. - # (This is error output. I guess speed is no consideration here.) - line += "contents=\"%s\"" % (self.contents - .replace('\n','\\n') - .replace('"','\\"') - .replace('\r','\\r') - .replace('\t','\\t')) - comma = True - else: - line += 'content is binary data' + if self.contents is not None: + line += "contents=" + repr(self.contents) comma = True if self.props: diff --git a/subversion/tests/cmdline/svntest/verify.py b/subversion/tests/cmdline/svntest/verify.py index 904a044..0fb7bcd 100644 --- a/subversion/tests/cmdline/svntest/verify.py +++ b/subversion/tests/cmdline/svntest/verify.py @@ -150,8 +150,9 @@ class ExpectedOutput(object): MESSAGE unless it is None, the expected lines, the ACTUAL lines, and a diff, all labeled with LABEL. """ - display_lines(message, self.expected, actual, label, label) - display_lines_diff(self.expected, actual, label, label) + e_label = label + ' (match_all=%s)' % (self.match_all,) + display_lines(message, self.expected, actual, e_label, label) + display_lines_diff(self.expected, actual, e_label, label) class AnyOutput(ExpectedOutput): @@ -181,12 +182,36 @@ class AnyOutput(ExpectedOutput): logger.warn(message) +def re_fullmatch(pattern, string, flags=0): + """If the whole STRING matches the regular expression PATTERN, + return a corresponding match object. + Based on re.fullmatch() in Python 3.4. + """ + if pattern.endswith('$'): + return re.match(pattern, string, flags) + + return re.match(pattern + '$', string, flags) + +def regex_fullmatch(rx, string): + """If the whole STRING matches the compiled regular expression RX, + return a corresponding match object. + Based on regex.fullmatch() in Python 3.4. + """ + if rx.pattern.endswith('$'): + return rx.match(string) + + return re_fullmatch(rx.pattern, string, rx.flags) + class RegexOutput(ExpectedOutput): """Matches a single regular expression. If MATCH_ALL is true, every actual line must match the RE. If MATCH_ALL is false, at least one actual line must match the RE. In any case, there must be at least one line of actual output. + + The RE must match a prefix of the actual line, in contrast to the + RegexListOutput and UnorderedRegexListOutput classes which match + whole lines. """ def __init__(self, expected, match_all=True): @@ -212,7 +237,8 @@ class RegexOutput(ExpectedOutput): return any(self.expected_re.match(line) for line in actual) def display_differences(self, message, label, actual): - display_lines(message, self.expected, actual, label + ' (regexp)', label) + e_label = label + ' (regexp, match_all=%s)' % (self.match_all,) + display_lines(message, self.expected, actual, e_label, label) def insert(self, index, line): self.expected.insert(index, line) @@ -228,6 +254,9 @@ class RegexListOutput(ExpectedOutput): ones. In any case, there must be at least one line of actual output. + + The REs must match whole actual lines, in contrast to the RegexOutput + class which matches a prefix of the actual line. """ def __init__(self, expected, match_all=True): @@ -243,18 +272,37 @@ class RegexListOutput(ExpectedOutput): if self.match_all: return (len(self.expected_res) == len(actual) and - all(e.match(a) for e, a in zip(self.expected_res, actual))) + all(regex_fullmatch(e, a) for e, a in zip(self.expected_res, actual))) i_expected = 0 for actual_line in actual: - if self.expected_res[i_expected].match(actual_line): + if regex_fullmatch(self.expected_res[i_expected], actual_line): i_expected += 1 if i_expected == len(self.expected_res): return True return False def display_differences(self, message, label, actual): - display_lines(message, self.expected, actual, label + ' (regexp)', label) + e_label = label + ' (regexp, match_all=%s)' % (self.match_all,) + display_lines(message, self.expected, actual, e_label, label) + + assert actual is not None + if not isinstance(actual, list): + actual = [actual] + + if self.match_all: + logger.warn('DIFF ' + label + ':') + if len(self.expected) != len(actual): + logger.warn('# Expected %d lines; actual %d lines' % + (len(self.expected), len(actual))) + for e, a in map(None, self.expected_res, actual): + if e is not None and a is not None and regex_fullmatch(e, a): + logger.warn("| " + a.rstrip()) + else: + if e is not None: + logger.warn("| -" + repr(e.pattern)) + if a is not None: + logger.warn("| +" + repr(a)) def insert(self, index, line): self.expected.insert(index, line) @@ -279,8 +327,9 @@ class UnorderedOutput(ExpectedOutput): return sorted(self.expected) == sorted(actual) def display_differences(self, message, label, actual): - display_lines(message, self.expected, actual, label + ' (unordered)', label) - display_lines_diff(self.expected, actual, label + ' (unordered)', label) + e_label = label + ' (unordered)' + display_lines(message, self.expected, actual, e_label, label) + display_lines_diff(sorted(self.expected), sorted(actual), e_label, label) class UnorderedRegexListOutput(ExpectedOutput): @@ -295,6 +344,9 @@ class UnorderedRegexListOutput(ExpectedOutput): expressions. The implementation matches each expression in turn to the first unmatched actual line that it can match, and does not try all the permutations when there are multiple possible matches. + + The REs must match whole actual lines, in contrast to the RegexOutput + class which matches a prefix of the actual line. """ def __init__(self, expected): @@ -305,13 +357,16 @@ class UnorderedRegexListOutput(ExpectedOutput): assert actual is not None if not isinstance(actual, list): actual = [actual] + else: + # copy the list so we can remove elements without affecting caller + actual = actual[:] if len(self.expected) != len(actual): return False for e in self.expected: expect_re = re.compile(e) for actual_line in actual: - if expect_re.match(actual_line): + if regex_fullmatch(expect_re, actual_line): actual.remove(actual_line) break else: @@ -320,9 +375,30 @@ class UnorderedRegexListOutput(ExpectedOutput): return True def display_differences(self, message, label, actual): - display_lines(message, self.expected, actual, - label + ' (regexp) (unordered)', label) + e_label = label + ' (regexp) (unordered)' + display_lines(message, self.expected, actual, e_label, label) + + assert actual is not None + if not isinstance(actual, list): + actual = [actual] + else: + # copy the list so we can remove elements without affecting caller + actual = actual[:] + logger.warn('DIFF ' + label + ':') + if len(self.expected) != len(actual): + logger.warn('# Expected %d lines; actual %d lines' % + (len(self.expected), len(actual))) + for e in self.expected: + expect_re = re.compile(e) + for actual_line in actual: + if regex_fullmatch(expect_re, actual_line): + actual.remove(actual_line) + break + else: + logger.warn("| -" + expect_re.pattern.rstrip()) + for a in actual: + logger.warn("| +" + a.rstrip()) class AlternateOutput(ExpectedOutput): """Matches any one of a list of ExpectedOutput instances. @@ -730,7 +806,8 @@ class DumpParser: self.parse_all_revisions() return self.parsed -def compare_dump_files(message, label, expected, actual, +def compare_dump_files(label_expected, label_actual, + expected, actual, ignore_uuid=False, expect_content_length_always=False, ignore_empty_prop_sections=False, @@ -772,6 +849,8 @@ def compare_dump_files(message, label, expected, actual, if parsed_expected != parsed_actual: print('DIFF of raw dumpfiles (including expected differences)') + print('--- ' + (label_expected or 'expected')) + print('+++ ' + (label_actual or 'actual')) print(''.join(ndiff(expected, actual))) raise svntest.Failure('DIFF of parsed dumpfiles (ignoring expected differences)\n' + '\n'.join(ndiff( diff --git a/subversion/tests/cmdline/svntest/wc.py b/subversion/tests/cmdline/svntest/wc.py index f805dc9..ddfc439 100644 --- a/subversion/tests/cmdline/svntest/wc.py +++ b/subversion/tests/cmdline/svntest/wc.py @@ -1092,8 +1092,7 @@ def svn_uri_quote(url): def python_sqlite_can_read_wc(): """Check if the Python builtin is capable enough to peek into wc.db""" - # Currently enough (1.7-1.9) - return svntest.sqlite3.sqlite_version_info >= (3, 6, 18) + return svntest.main.python_sqlite_can_read_our_wc_db() def open_wc_db(local_path): """Open the SQLite DB for the WC path LOCAL_PATH. diff --git a/subversion/tests/cmdline/svnversion_tests.py b/subversion/tests/cmdline/svnversion_tests.py index 2ed6e46..e9d0927 100755 --- a/subversion/tests/cmdline/svnversion_tests.py +++ b/subversion/tests/cmdline/svnversion_tests.py @@ -71,9 +71,8 @@ def svnversion_test(sbox): expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')}) expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.tweak('A/mu', wc_rev=2) - if svntest.actions.run_and_verify_commit(wc_dir, - expected_output, expected_status): - raise svntest.Failure + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, expected_status) # Unmodified, mixed svntest.actions.run_and_verify_svnversion(wc_dir, repo_url, @@ -98,13 +97,12 @@ def svnversion_test(sbox): + 'appended mu text') expected_disk.tweak('iota', contents=expected_disk.desc['A/D/gamma'].contents) - if svntest.actions.run_and_verify_switch(wc_dir, iota_path, gamma_url, - expected_output, - expected_disk, - expected_status, - [], - False, '--ignore-ancestry'): - raise svntest.Failure + svntest.actions.run_and_verify_switch(wc_dir, iota_path, gamma_url, + expected_output, + expected_disk, + expected_status, + [], + False, '--ignore-ancestry') # Prop modified, mixed, part wc switched svntest.actions.run_and_verify_svnversion(wc_dir, repo_url, diff --git a/subversion/tests/cmdline/trans_tests.py b/subversion/tests/cmdline/trans_tests.py index 0cab75e..71ca45e 100755 --- a/subversion/tests/cmdline/trans_tests.py +++ b/subversion/tests/cmdline/trans_tests.py @@ -814,7 +814,8 @@ def props_only_file_update(sbox): ] # Create r2 with iota's contents and svn:keywords modified - open(iota_path, 'w').writelines(content) + with open(iota_path, 'w') as f: + f.writelines(content) svntest.main.run_svn(None, 'propset', 'svn:keywords', 'Author', iota_path) expected_output = wc.State(wc_dir, { @@ -831,7 +832,8 @@ def props_only_file_update(sbox): # Create r3 that drops svn:keywords # put the content back to its untranslated form - open(iota_path, 'w').writelines(content) + with open(iota_path, 'w') as f: + f.writelines(content) svntest.main.run_svn(None, 'propdel', 'svn:keywords', iota_path) diff --git a/subversion/tests/cmdline/tree_conflict_tests.py b/subversion/tests/cmdline/tree_conflict_tests.py index b3335b2..61f3231 100755 --- a/subversion/tests/cmdline/tree_conflict_tests.py +++ b/subversion/tests/cmdline/tree_conflict_tests.py @@ -473,6 +473,7 @@ def ensure_tree_conflict(sbox, operation, run_and_verify_svn(expected_stdout, [], 'merge', '--allow-mixed-revisions', + '--accept=postpone', '-r', str(source_left_rev) + ':' + str(source_right_rev), source_url, target_path) else: @@ -1096,13 +1097,15 @@ def at_directory_external(sbox): svntest.main.run_svn(None, 'update', wc_dir) # r3: modify ^/A/B/E/alpha - open(sbox.ospath('A/B/E/alpha'), 'a').write('This is still A/B/E/alpha.\n') + with open(sbox.ospath('A/B/E/alpha'), 'a') as f: + f.write('This is still A/B/E/alpha.\n') svntest.main.run_svn(None, 'commit', '-m', 'file mod', wc_dir) svntest.main.run_svn(None, 'update', wc_dir) merge_rev = svntest.main.youngest(sbox.repo_dir) # r4: create ^/A/B/E/alpha2 - open(sbox.ospath('A/B/E/alpha2'), 'a').write("This is the file 'alpha2'.\n") + with open(sbox.ospath('A/B/E/alpha2'), 'a') as f: + f.write("This is the file 'alpha2'.\n") svntest.main.run_svn(None, 'add', sbox.ospath('A/B/E/alpha2')) svntest.main.run_svn(None, 'commit', '-m', 'file add', wc_dir) svntest.main.run_svn(None, 'update', wc_dir) @@ -1503,6 +1506,47 @@ def update_delete_mixed_rev(sbox): } run_and_verify_info([expected_info], sbox.repo_url + '/A/B/E/alpha2') +# NB: This test will run forever if the bug it is testing for is present! +def local_missing_dir_endless_loop(sbox): + "endless loop when resolving local-missing dir" + + sbox.build() + wc_dir = sbox.wc_dir + sbox.simple_copy('A', 'A1') + sbox.simple_commit() + sbox.simple_update() + sbox.simple_move('A/B', 'A/B2') + sbox.simple_commit() + sbox.simple_update() + main.file_append_binary(sbox.ospath("A/B2/lambda"), "This is more content.\n") + sbox.simple_commit() + sbox.simple_update() + + # Create a config which enables the interactive conflict resolver + config_contents = '''\ +[auth] +password-stores = + +[miscellany] +interactive-conflicts = true +''' + config_dir = sbox.create_config_dir(config_contents) + + # Bug: 'svn' keeps retrying interactive conflict resolution while the library + # keeps signalling 'SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE' -> endless loop + main.run_svn("Tree conflict on '%s'" % sbox.ospath("A1/B2"), + 'merge', '-c4', '^/A', sbox.ospath('A1'), + '--config-dir', config_dir, '--force-interactive') + + # If everything works as expected the resolver will recommended a + # resolution option and 'svn' will resolve the conflict automatically. + # Verify that 'A1/B/lambda' contains the merged content: + contents = open(sbox.ospath('A1/B/lambda'), 'rb').readlines() + svntest.verify.compare_and_display_lines( + "A1/B/lambda has unexpectected contents", sbox.ospath("A1/B/lambda"), + [ "This is the file 'lambda'.\n", "This is more content.\n"], contents) + + ####################################################################### # Run the tests @@ -1534,6 +1578,7 @@ test_list = [ None, actual_only_node_behaviour, update_dir_with_not_present, update_delete_mixed_rev, + local_missing_dir_endless_loop, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/update_tests.py b/subversion/tests/cmdline/update_tests.py index 16c7237..185fcb1 100755 --- a/subversion/tests/cmdline/update_tests.py +++ b/subversion/tests/cmdline/update_tests.py @@ -480,7 +480,7 @@ def update_to_rev_zero(sbox): def receive_overlapping_same_change(sbox): "overlapping identical changes should not conflict" - ### (See http://subversion.tigris.org/issues/show_bug.cgi?id=682.) + ### (See https://issues.apache.org/jira/browse/SVN-682.) ### ### How this test works: ### @@ -6719,6 +6719,7 @@ def update_conflict_details(sbox): # Keywords should be updated in local file even if text change is shortcut # (due to the local change being the same as the incoming change, for example). @XFail() +@Issue(4585) def update_keywords_on_shortcut(sbox): "update_keywords_on_shortcut" diff --git a/subversion/tests/cmdline/upgrade_tests.py b/subversion/tests/cmdline/upgrade_tests.py index 43258f6..c60dab5 100755 --- a/subversion/tests/cmdline/upgrade_tests.py +++ b/subversion/tests/cmdline/upgrade_tests.py @@ -392,7 +392,8 @@ def xml_entries_relocate(path, from_url, to_url): entries = os.path.join(path, adm_name, 'entries') txt = open(entries).read().replace('url="' + from_url, 'url="' + to_url) os.chmod(entries, svntest.main.S_ALL_RWX) - open(entries, 'w').write(txt) + with open(entries, 'w') as f: + f.write(txt) for dirent in os.listdir(path): item_path = os.path.join(path, dirent) @@ -410,7 +411,8 @@ def simple_entries_replace(path, from_url, to_url): entries = os.path.join(path, adm_name, 'entries') txt = open(entries).read().replace(from_url, to_url) os.chmod(entries, svntest.main.S_ALL_RWX) - open(entries, 'wb').write(txt.encode()) + with open(entries, 'wb') as f: + f.write(txt.encode()) for dirent in os.listdir(path): item_path = os.path.join(path, dirent) diff --git a/subversion/tests/libsvn_client/conflicts-test.c b/subversion/tests/libsvn_client/conflicts-test.c index 0bcb464..67e516a 100644 --- a/subversion/tests/libsvn_client/conflicts-test.c +++ b/subversion/tests/libsvn_client/conflicts-test.c @@ -164,6 +164,7 @@ static const char *trunk_path = "A"; static const char *branch_path = "A_branch"; static const char *branch2_path = "A_branch2"; static const char *new_file_name = "newfile.txt"; +static const char *new_file2_name = "newfile2.txt"; static const char *new_file_name_branch = "newfile-on-branch.txt"; static const char *deleted_file_name = "mu"; static const char *deleted_dir_name = "B"; @@ -540,7 +541,7 @@ create_wc_with_dir_add_vs_dir_add_merge_conflict( /* Now move the new directory to the colliding path. */ new_dir_path = svn_relpath_join(trunk_path, new_dir_name, b->pool); SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); - sbox_wc_move(b, move_src_path, new_dir_path); + SVN_ERR(sbox_wc_move(b, move_src_path, new_dir_path)); SVN_ERR(sbox_wc_commit(b, "")); } new_dir_path = svn_relpath_join(branch_path, new_dir_name, b->pool); @@ -608,7 +609,6 @@ create_wc_with_dir_add_vs_dir_add_merge_conflict( svn_client_conflict_option_postpone, svn_client_conflict_option_accept_current_wc_state, svn_client_conflict_option_incoming_add_ignore, - svn_client_conflict_option_incoming_added_dir_merge, svn_client_conflict_option_incoming_added_dir_replace, svn_client_conflict_option_incoming_added_dir_replace_and_merge, -1 /* end of list */ @@ -1292,6 +1292,7 @@ test_merge_incoming_added_dir_replace_and_merge2(const svn_test_opts_t *opts, static svn_error_t * create_wc_with_incoming_delete_file_merge_conflict(svn_test__sandbox_t *b, svn_boolean_t move, + svn_boolean_t move_unrelated, svn_boolean_t do_switch) { svn_client_ctx_t *ctx; @@ -1313,6 +1314,17 @@ create_wc_with_incoming_delete_file_merge_conflict(svn_test__sandbox_t *b, deleted_path = svn_relpath_join(trunk_path, deleted_file_name, b->pool); move_target_path = svn_relpath_join(trunk_path, new_file_name, b->pool); SVN_ERR(sbox_wc_move(b, deleted_path, move_target_path)); + if (move_unrelated) + { + /* Move an unrelated file on trunk as part of the same commit. */ + deleted_path = svn_relpath_join(trunk_path, + svn_relpath_join(deleted_dir_name, + deleted_dir_child, + b->pool), + b->pool); + move_target_path = svn_relpath_join(trunk_path, new_file2_name, b->pool); + SVN_ERR(sbox_wc_move(b, deleted_path, move_target_path)); + } SVN_ERR(sbox_wc_commit(b, "")); } else @@ -1378,7 +1390,8 @@ test_merge_incoming_delete_file_ignore(const svn_test_opts_t *opts, SVN_ERR(svn_test__sandbox_create(b, "merge_incoming_delete_file_ignore", opts, pool)); - SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, FALSE, FALSE)); + SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, FALSE, FALSE, + FALSE)); /* Resolve the tree conflict. */ SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); @@ -1470,7 +1483,8 @@ test_merge_incoming_delete_file_accept(const svn_test_opts_t *opts, SVN_ERR(svn_test__sandbox_create(b, "merge_incoming_delete_file_accept", opts, pool)); - SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, FALSE, FALSE)); + SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, FALSE, FALSE, + FALSE)); /* Resolve the tree conflict. */ SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); @@ -1565,7 +1579,8 @@ test_merge_incoming_move_file_text_merge(const svn_test_opts_t *opts, SVN_ERR(svn_test__sandbox_create(b, "merge_incoming_move_file_text_merge", opts, pool)); - SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, TRUE, FALSE)); + SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, TRUE, FALSE, + FALSE)); /* Resolve the tree conflict. */ SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); @@ -1971,7 +1986,8 @@ test_switch_incoming_move_file_text_merge(const svn_test_opts_t *opts, SVN_ERR(svn_test__sandbox_create(b, "switch_incoming_move_file_text_merge", opts, pool)); - SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, TRUE, TRUE)); + SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, TRUE, FALSE, + TRUE)); /* Resolve the tree conflict. */ SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); @@ -2888,10 +2904,13 @@ test_merge_incoming_edit_file_moved_away(const svn_test_opts_t *opts, svn_client_ctx_t *ctx; svn_opt_revision_t opt_rev; svn_client_conflict_t *conflict; + apr_array_header_t *options; + svn_client_conflict_option_t *option; svn_boolean_t text_conflicted; apr_array_header_t *props_conflicted; svn_boolean_t tree_conflicted; svn_stringbuf_t *buf; + apr_array_header_t *possible_moved_to_abspaths; SVN_ERR(svn_test__sandbox_create( b, "merge_incoming_edit_file_moved_away", opts, pool)); @@ -2953,6 +2972,19 @@ test_merge_incoming_edit_file_moved_away(const svn_test_opts_t *opts, SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, pool)); } + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict, + ctx, b->pool, + b->pool)); + option = svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_local_move_file_text_merge); + SVN_TEST_ASSERT(option != NULL); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A1/mu-moved")); + /* Resolve the tree conflict by applying the incoming edit to the local * move destination "mu-moved". */ SVN_ERR(svn_client_conflict_tree_resolve_by_id( @@ -4966,7 +4998,7 @@ test_cherry_pick_post_move_edit(const svn_test_opts_t *opts, svn_client_conflict_option_id_t expected_opts[] = { svn_client_conflict_option_postpone, svn_client_conflict_option_accept_current_wc_state, - svn_client_conflict_option_local_move_file_text_merge, + svn_client_conflict_option_sibling_move_file_text_merge, -1 /* end of list */ }; SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, @@ -4976,7 +5008,7 @@ test_cherry_pick_post_move_edit(const svn_test_opts_t *opts, /* Try to resolve the conflict. */ SVN_ERR(svn_client_conflict_tree_resolve_by_id( conflict, - svn_client_conflict_option_local_move_file_text_merge, + svn_client_conflict_option_sibling_move_file_text_merge, ctx, b->pool)); /* The node "A1/mu-moved" should no longer exist. */ @@ -4987,7 +5019,14 @@ test_cherry_pick_post_move_edit(const svn_test_opts_t *opts, /* And "A1/mu" should have expected contents. */ SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, "A1/mu"), pool)); - SVN_TEST_STRING_ASSERT(buf->data, "More modified content." APR_EOL_STR); + SVN_TEST_STRING_ASSERT(buf->data, + "<<<<<<< .working" "\n" + "This is the file 'mu'." "\n" + "||||||| .old" "\n" + "Modified content." APR_EOL_STR + "=======" "\n" + "More modified content." APR_EOL_STR + ">>>>>>> .new" "\n"); return SVN_NO_ERROR; } @@ -5179,6 +5218,977 @@ test_merge_incoming_move_dir_across_branches(const svn_test_opts_t *opts, return SVN_NO_ERROR; } +static svn_error_t * +test_update_incoming_delete_locally_deleted_file(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_status3_t *wc_status; + + SVN_ERR(svn_test__sandbox_create( + b, "update_incoming_delete_locally_deleted_file", opts, pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); + /* Delete the file. */ + SVN_ERR(sbox_wc_delete(b, "A/mu")); + SVN_ERR(sbox_wc_commit(b, "")); + /* Update to revision before delete. */ + SVN_ERR(sbox_wc_update(b, "", 1)); + /* Delete the file locally. */ + SVN_ERR(sbox_wc_delete(b, "A/mu")); + /* Attempt an update to HEAD. */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* We should have a tree conflict in the file "mu". */ + SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool)); + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, "A/mu"), + ctx, pool, pool)); + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, pool, pool)); + SVN_TEST_ASSERT(!text_conflicted); + SVN_TEST_INT_ASSERT(props_conflicted->nelts, 0); + SVN_TEST_ASSERT(tree_conflicted); + + /* Check available tree conflict resolution options. */ + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_delete_accept, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, pool)); + + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_delete_accept, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, pool)); + } + + /* Resolve the tree conflict accepting the incoming deletion. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, svn_client_conflict_option_incoming_delete_accept, + ctx, pool)); + + /* Check the status. */ + SVN_ERR(svn_wc_status3(&wc_status, ctx->wc_ctx, sbox_wc_path(b, "A/mu"), + pool, pool)); + SVN_TEST_INT_ASSERT(wc_status->kind, svn_node_unknown); + SVN_TEST_ASSERT(!wc_status->versioned); + SVN_TEST_ASSERT(!wc_status->conflicted); + SVN_TEST_INT_ASSERT(wc_status->node_status, svn_wc_status_none); + SVN_TEST_INT_ASSERT(wc_status->text_status, svn_wc_status_none); + SVN_TEST_INT_ASSERT(wc_status->prop_status, svn_wc_status_none); + SVN_TEST_INT_ASSERT(wc_status->actual_kind, svn_node_none); + + return SVN_NO_ERROR; +} + +/* A helper function which prepares a working copy for the test below. */ +static svn_error_t * +create_wc_with_added_dir_conflict_across_branches(svn_test__sandbox_t *b, + svn_client_ctx_t *ctx) +{ + const char *trunk_url; + const char *branch_url; + svn_opt_revision_t opt_rev; + const char *new_dir_path; + const char *new_file_path; + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); + + /* Create a branch of node "A". */ + SVN_ERR(sbox_wc_copy(b, trunk_path, branch_path)); + SVN_ERR(sbox_wc_commit(b, "")); + + /* Create a second branch ("branch2") of node "A". */ + SVN_ERR(sbox_wc_copy(b, trunk_path, branch2_path)); + SVN_ERR(sbox_wc_commit(b, "")); + + /* Add directories with differing content to both branches. */ + new_dir_path = svn_relpath_join(branch_path, new_dir_name, b->pool); + SVN_ERR(sbox_wc_mkdir(b, new_dir_path)); + new_file_path = svn_relpath_join(new_dir_path, new_file_name, b->pool); + SVN_ERR(sbox_file_write(b, new_file_path, + "This is a new file on branch 1\n")); + SVN_ERR(sbox_wc_add(b, new_file_path)); + SVN_ERR(sbox_wc_commit(b, "")); + + new_dir_path = svn_relpath_join(branch2_path, new_dir_name, b->pool); + SVN_ERR(sbox_wc_mkdir(b, new_dir_path)); + new_file_path = svn_relpath_join(new_dir_path, new_file_name, b->pool); + SVN_ERR(sbox_file_write(b, new_file_path, + "This is a new file on branch 2\n")); + SVN_ERR(sbox_wc_add(b, new_file_path)); + SVN_ERR(sbox_wc_commit(b, "")); + + /* Merge the differences between trunk and branch into branch2. + * This merge should raise an add vs. add conflict on the new directory. */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + opt_rev.kind = svn_opt_revision_head; + opt_rev.value.number = SVN_INVALID_REVNUM; + trunk_url = apr_pstrcat(b->pool, b->repos_url, "/", trunk_path, + SVN_VA_NULL); + branch_url = apr_pstrcat(b->pool, b->repos_url, "/", branch2_path, + SVN_VA_NULL); + opt_rev.kind = svn_opt_revision_head; + opt_rev.value.number = SVN_INVALID_REVNUM; + SVN_ERR(svn_client_merge5(trunk_url, &opt_rev, branch_url, &opt_rev, + sbox_wc_path(b, branch2_path), + svn_depth_infinity, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + NULL, ctx, b->pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +test_merge_two_added_dirs_assertion_failure(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_status3_t *wc_status; + const char *new_dir_path; + + SVN_ERR(svn_test__sandbox_create( + b, "test_merge_two_added_dirs_assertion_failure", opts, pool)); + + SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool)); + SVN_ERR(create_wc_with_added_dir_conflict_across_branches(b, ctx)); + + /* We should have a tree conflict in the directory "A_branch2/newdir". */ + new_dir_path = svn_relpath_join(branch2_path, new_dir_name, b->pool); + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, new_dir_path), + ctx, pool, pool)); + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, pool, pool)); + SVN_TEST_ASSERT(!text_conflicted); + SVN_TEST_INT_ASSERT(props_conflicted->nelts, 0); + SVN_TEST_ASSERT(tree_conflicted); + + /* Check available tree conflict resolution options. */ + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_add_ignore, + svn_client_conflict_option_incoming_added_dir_replace, + svn_client_conflict_option_incoming_added_dir_replace_and_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, pool)); + } + + /* This call used to run into an assertion failure (start_rev > end_rev). */ + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, pool)); + + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_add_ignore, + svn_client_conflict_option_incoming_added_dir_replace, + svn_client_conflict_option_incoming_added_dir_replace_and_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, pool)); + } + + /* Resolve the tree conflict by replace + merge. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, + svn_client_conflict_option_incoming_added_dir_replace_and_merge, + ctx, pool)); + + /* Check the status. */ + SVN_ERR(svn_wc_status3(&wc_status, ctx->wc_ctx, sbox_wc_path(b, new_dir_path), + pool, pool)); + SVN_TEST_INT_ASSERT(wc_status->kind, svn_node_dir); + SVN_TEST_ASSERT(wc_status->versioned); + SVN_TEST_ASSERT(!wc_status->conflicted); + SVN_TEST_INT_ASSERT(wc_status->node_status, svn_wc_status_replaced); + SVN_TEST_INT_ASSERT(wc_status->text_status, svn_wc_status_normal); + SVN_TEST_INT_ASSERT(wc_status->prop_status, svn_wc_status_none); + SVN_TEST_INT_ASSERT(wc_status->actual_kind, svn_node_dir); + + return SVN_NO_ERROR; +} + +/* Test for issue #4766: resolver adds unrelated moves to move target list */ +static svn_error_t * +test_merge_incoming_delete_file_unrelated_move(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_client_ctx_t *ctx; + const char *deleted_path; + svn_client_conflict_t *conflict; + svn_boolean_t tree_conflicted; + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + struct status_baton sb; + struct svn_client_status_t *status; + svn_opt_revision_t opt_rev; + apr_array_header_t *possible_moved_to_repos_relpaths; + apr_array_header_t *options; + svn_client_conflict_option_t *option; + const char *new_file_path; + const char *moved_to_repos_path; + svn_node_kind_t kind; + svn_stringbuf_t *buf; + + SVN_ERR(svn_test__sandbox_create(b, + "merge_incoming_delete_file_unrelated_move", + opts, pool)); + + SVN_ERR(create_wc_with_incoming_delete_file_merge_conflict(b, TRUE, TRUE, + FALSE)); + + /* Resolve the tree conflict. */ + SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); + deleted_path = svn_relpath_join(branch_path, deleted_file_name, b->pool); + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, deleted_path), + ctx, b->pool, b->pool)); + + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_delete_ignore, + svn_client_conflict_option_incoming_delete_accept, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_move_file_text_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict, + ctx, b->pool, + b->pool)); + option = svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_incoming_move_file_text_merge); + SVN_TEST_ASSERT(option != NULL); + + /* Assert that only one move target candidate has been found in repository. */ + SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + &possible_moved_to_repos_relpaths, option, b->pool, b->pool)); + + SVN_TEST_INT_ASSERT(possible_moved_to_repos_relpaths->nelts, 1); + moved_to_repos_path = svn_relpath_join(trunk_path, new_file_name, b->pool); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 0, const char *), moved_to_repos_path); + + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, svn_client_conflict_option_incoming_move_file_text_merge, + ctx, b->pool)); + + /* Ensure that the deleted file has the expected status. */ + opt_rev.kind = svn_opt_revision_working; + sb.result_pool = b->pool; + SVN_ERR(svn_client_status6(NULL, ctx, sbox_wc_path(b, deleted_path), + &opt_rev, svn_depth_unknown, TRUE, TRUE, + TRUE, TRUE, FALSE, TRUE, NULL, + status_func, &sb, b->pool)); + status = sb.status; + SVN_TEST_ASSERT(status->kind == svn_node_file); + SVN_TEST_ASSERT(status->versioned); + SVN_TEST_ASSERT(!status->conflicted); + SVN_TEST_ASSERT(status->node_status == svn_wc_status_deleted); + SVN_TEST_ASSERT(status->text_status == svn_wc_status_normal); + SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none); + SVN_TEST_ASSERT(!status->copied); + SVN_TEST_ASSERT(!status->switched); + SVN_TEST_ASSERT(!status->file_external); + SVN_TEST_ASSERT(status->moved_from_abspath == NULL); + new_file_path = svn_relpath_join(branch_path, new_file_name, b->pool); + SVN_TEST_STRING_ASSERT(status->moved_to_abspath, + sbox_wc_path(b, new_file_path)); + + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, deleted_path), + ctx, b->pool, b->pool)); + + /* The file should not be in conflict. */ + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, b->pool, b->pool)); + SVN_TEST_ASSERT(!text_conflicted && + props_conflicted->nelts == 0 && + !tree_conflicted); + + /* Ensure that the moved file has the expected status. */ + opt_rev.kind = svn_opt_revision_working; + sb.result_pool = b->pool; + SVN_ERR(svn_client_status6(NULL, ctx, sbox_wc_path(b, new_file_path), + &opt_rev, svn_depth_unknown, TRUE, TRUE, + TRUE, TRUE, FALSE, TRUE, NULL, + status_func, &sb, b->pool)); + status = sb.status; + SVN_TEST_ASSERT(status->kind == svn_node_file); + SVN_TEST_ASSERT(status->versioned); + SVN_TEST_ASSERT(!status->conflicted); + SVN_TEST_ASSERT(status->node_status == svn_wc_status_added); + SVN_TEST_ASSERT(status->text_status == svn_wc_status_normal); + SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none); + SVN_TEST_ASSERT(status->copied); + SVN_TEST_ASSERT(!status->switched); + SVN_TEST_ASSERT(!status->file_external); + SVN_TEST_STRING_ASSERT(status->moved_from_abspath, + sbox_wc_path(b, deleted_path)); + SVN_TEST_ASSERT(status->moved_to_abspath == NULL); + + /* Ensure that the original file was removed. */ + SVN_ERR(svn_io_check_path(sbox_wc_path(b, deleted_path), &kind, b->pool)); + SVN_TEST_ASSERT(kind == svn_node_none); + + /* Ensure that the moved file has the expected content. */ + SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, new_file_path), + b->pool)); + SVN_TEST_STRING_ASSERT(buf->data, modified_file_on_branch_content); + + /* Ensure that the unrelated moved file has the expected status. */ + opt_rev.kind = svn_opt_revision_working; + sb.result_pool = b->pool; + SVN_ERR(svn_client_status6(NULL, ctx, + sbox_wc_path(b, svn_relpath_join(branch_path, + new_file2_name, b->pool)), + &opt_rev, svn_depth_unknown, TRUE, TRUE, + TRUE, TRUE, FALSE, TRUE, NULL, + status_func, &sb, b->pool)); + status = sb.status; + SVN_TEST_ASSERT(status->kind == svn_node_file); + SVN_TEST_ASSERT(status->versioned); + SVN_TEST_ASSERT(!status->conflicted); + SVN_TEST_ASSERT(status->node_status == svn_wc_status_added); + SVN_TEST_ASSERT(status->text_status == svn_wc_status_normal); + SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none); + SVN_TEST_ASSERT(status->copied); + SVN_TEST_ASSERT(!status->switched); + SVN_TEST_ASSERT(!status->file_external); + SVN_TEST_ASSERT(status->moved_from_abspath == NULL); + SVN_TEST_ASSERT(status->moved_to_abspath == NULL); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_cherry_pick_post_move_edit_dir(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + const char *trunk_url; + svn_opt_revision_t peg_rev; + apr_array_header_t *ranges_to_merge; + svn_opt_revision_range_t merge_range; + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + svn_boolean_t tree_conflicted; + svn_stringbuf_t *buf; + + SVN_ERR(svn_test__sandbox_create(b, + "test_cherry_pick_post_move_edit_dir", + opts, pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */ + /* Create a copy of node "A". */ + SVN_ERR(sbox_wc_copy(b, "A", "A1")); + SVN_ERR(sbox_wc_commit(b, "")); /* r2 */ + /* On "trunk", move the directory B. */ + SVN_ERR(sbox_wc_move(b, "A/B", "A/B-moved")); + SVN_ERR(sbox_wc_commit(b, "")); /* r3 */ + /* On "trunk", edit B-moved/lambda. This will be r4. */ + SVN_ERR(sbox_file_write(b, "A/B-moved/lambda", "Modified content." + APR_EOL_STR)); + SVN_ERR(sbox_wc_commit(b, "")); /* r4 */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* Perform a cherry-pick merge of r4 from A to A1. */ + SVN_ERR(svn_test__create_client_ctx(&ctx, b, b->pool)); + trunk_url = apr_pstrcat(b->pool, b->repos_url, "/A", SVN_VA_NULL); + peg_rev.kind = svn_opt_revision_number; + peg_rev.value.number = 4; + merge_range.start.kind = svn_opt_revision_number; + merge_range.start.value.number = 3; + merge_range.end.kind = svn_opt_revision_number; + merge_range.end.value.number = 4; + ranges_to_merge = apr_array_make(b->pool, 1, + sizeof(svn_opt_revision_range_t *)); + APR_ARRAY_PUSH(ranges_to_merge, svn_opt_revision_range_t *) = &merge_range; + /* This should raise a "local missing vs incoming edit" conflict. */ + SVN_ERR(svn_client_merge_peg5(trunk_url, ranges_to_merge, &peg_rev, + sbox_wc_path(b, "A1"), svn_depth_infinity, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + NULL, ctx, b->pool)); + + SVN_ERR(svn_client_conflict_get(&conflict, + sbox_wc_path(b, "A1/B-moved"), + ctx, b->pool, b->pool)); + SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted, + conflict, b->pool, b->pool)); + SVN_TEST_ASSERT(tree_conflicted); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_sibling_move_dir_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + /* Try to resolve the conflict. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, + svn_client_conflict_option_sibling_move_dir_merge, + ctx, b->pool)); + + /* The node "B-moved" should no longer exist. */ + SVN_TEST_ASSERT_ERROR(svn_client_conflict_get(&conflict, + sbox_wc_path(b, "A1/B-moved"), + ctx, pool, pool), + SVN_ERR_WC_PATH_NOT_FOUND); + + /* And "A1/B/lambda" should have expected contents. */ + SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, "A1/B/lambda"), pool)); + SVN_TEST_STRING_ASSERT(buf->data, "Modified content." APR_EOL_STR); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_local_missing_abiguous_moves(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_opt_revision_t opt_rev; + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + apr_array_header_t *options; + svn_client_conflict_option_t *option; + apr_array_header_t *possible_moved_to_repos_relpaths; + apr_array_header_t *possible_moved_to_abspaths; + struct status_baton sb; + struct svn_client_status_t *status; + svn_stringbuf_t *buf; + + SVN_ERR(svn_test__sandbox_create(b, "local_missing_ambiguous_moves", opts, + pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */ + + /* Create a copy of node "A" (the "trunk") to "A1" (the "branch"). */ + SVN_ERR(sbox_wc_copy(b, "A", "A1")); + SVN_ERR(sbox_wc_commit(b, "")); /* r2 */ + + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + /* Copy a file across branch boundaries (gives ambiguous WC targets later). */ + SVN_ERR(sbox_wc_copy(b, "A/mu", "A1/mu-copied-from-A")); + /* Create an ambiguous move with the "trunk". */ + SVN_ERR(sbox_wc_copy(b, "A/mu", "A/mu-copied")); + SVN_ERR(sbox_wc_move(b, "A/mu", "A/mu-moved")); + SVN_ERR(sbox_wc_commit(b, "")); /* r3 */ + + /* Modify the moved file on the "branch". */ + SVN_ERR(sbox_file_write(b, "A1/mu", "Modified content." APR_EOL_STR)); + SVN_ERR(sbox_wc_commit(b, "")); /* r4 */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* Merge "A1" ("branch") into "A" ("trunk"). */ + opt_rev.kind = svn_opt_revision_head; + opt_rev.value.number = SVN_INVALID_REVNUM; + SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool)); + SVN_ERR(svn_client_merge_peg5(svn_path_url_add_component2(b->repos_url, "A1", + pool), + NULL, &opt_rev, sbox_wc_path(b, "A"), + svn_depth_infinity, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + NULL, ctx, pool)); + + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, "A/mu"), + ctx, b->pool, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_local_move_file_text_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict, + ctx, b->pool, + b->pool)); + option = svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_local_move_file_text_merge); + SVN_TEST_ASSERT(option != NULL); + + /* + * Possible repository destinations for moved-away 'A/mu' are: + * (1): '^/A/mu-copied' + * (2): '^/A/mu-moved' + * (3): '^/A1/mu-copied-from-A' + */ + SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + &possible_moved_to_repos_relpaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_repos_relpaths->nelts, 3); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 0, const char *), + "A/mu-copied"); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 1, const char *), + "A/mu-moved"); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 2, const char *), + "A1/mu-copied-from-A"); + + /* Move target for "A/mu-copied" (selected by default) is not ambiguous. */ + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/mu-copied")); + + /* Move target for "A/mu-moved" is not ambiguous. */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1, + ctx, b->pool)); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/mu-moved")); + + /* Select move target "A1/mu-copied-from-A". */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 2, + ctx, b->pool)); + + /* + * Possible working copy destinations for moved-away 'A/mu' are: + * (1): 'A/mu-copied-from-A' + * (2): 'A1/mu-copied-from-A' + */ + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 2); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/mu-copied-from-A")); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 1, const char *), + sbox_wc_path(b, "A1/mu-copied-from-A")); + + /* Select move target "A/mu-moved". */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1, + ctx, b->pool)); + + /* Resolve the tree conflict. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, + svn_client_conflict_option_local_move_file_text_merge, ctx, + b->pool)); + + /* The node "A/mu" should no longer exist. */ + SVN_TEST_ASSERT_ERROR(svn_client_conflict_get(&conflict, + sbox_wc_path(b, "A/mu"), + ctx, pool, pool), + SVN_ERR_WC_PATH_NOT_FOUND); + + /* Ensure that the merged file has the expected status. */ + opt_rev.kind = svn_opt_revision_working; + sb.result_pool = b->pool; + SVN_ERR(svn_client_status6(NULL, ctx, sbox_wc_path(b, "A/mu-moved"), + &opt_rev, svn_depth_unknown, TRUE, TRUE, + TRUE, TRUE, FALSE, TRUE, NULL, + status_func, &sb, b->pool)); + status = sb.status; + SVN_TEST_ASSERT(status->kind == svn_node_file); + SVN_TEST_ASSERT(status->versioned); + SVN_TEST_ASSERT(!status->conflicted); + SVN_TEST_ASSERT(status->node_status == svn_wc_status_modified); + SVN_TEST_ASSERT(status->text_status == svn_wc_status_modified); + SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none); + SVN_TEST_ASSERT(!status->copied); + SVN_TEST_ASSERT(!status->switched); + SVN_TEST_ASSERT(!status->file_external); + SVN_TEST_ASSERT(status->moved_from_abspath == NULL); + SVN_TEST_ASSERT(status->moved_to_abspath == NULL); + + /* And it should have expected contents. */ + SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, "A/mu-moved"), + pool)); + SVN_TEST_STRING_ASSERT(buf->data, "Modified content." APR_EOL_STR); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_local_missing_abiguous_moves_dir(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_opt_revision_t opt_rev; + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + apr_array_header_t *options; + svn_client_conflict_option_t *option; + apr_array_header_t *possible_moved_to_repos_relpaths; + apr_array_header_t *possible_moved_to_abspaths; + struct status_baton sb; + struct svn_client_status_t *status; + svn_stringbuf_t *buf; + + SVN_ERR(svn_test__sandbox_create(b, "local_missing_ambiguous_moves_dir", + opts, pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */ + + /* Create a copy of node "A" (the "trunk") to "A1" (the "branch"). */ + SVN_ERR(sbox_wc_copy(b, "A", "A1")); + SVN_ERR(sbox_wc_commit(b, "")); /* r2 */ + + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + /* Copy a dir across branch boundaries (gives ambiguous WC targets later). */ + SVN_ERR(sbox_wc_copy(b, "A/B", "A1/B-copied-from-A")); + /* Create an ambiguous move with the "trunk". */ + SVN_ERR(sbox_wc_copy(b, "A/B", "A/B-copied")); + SVN_ERR(sbox_wc_move(b, "A/B", "A/B-moved")); + SVN_ERR(sbox_wc_commit(b, "")); /* r3 */ + + /* Modify a file in the moved directory on the "branch". */ + SVN_ERR(sbox_file_write(b, "A1/B/lambda", "Modified content." APR_EOL_STR)); + SVN_ERR(sbox_wc_commit(b, "")); /* r4 */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* Merge "A1" ("branch") into "A" ("trunk"). */ + opt_rev.kind = svn_opt_revision_head; + opt_rev.value.number = SVN_INVALID_REVNUM; + SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool)); + SVN_ERR(svn_client_merge_peg5(svn_path_url_add_component2(b->repos_url, "A1", + pool), + NULL, &opt_rev, sbox_wc_path(b, "A"), + svn_depth_infinity, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + NULL, ctx, pool)); + + SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, "A/B"), + ctx, b->pool, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_local_move_dir_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict, + ctx, b->pool, + b->pool)); + option = svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_local_move_dir_merge); + SVN_TEST_ASSERT(option != NULL); + + /* + * Possible repository destinations for moved-away 'A/mu' are: + * (1): '^/A/B-copied' + * (2): '^/A/B-moved' + * (3): '^/A1/B-copied-from-A' + */ + SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + &possible_moved_to_repos_relpaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_repos_relpaths->nelts, 3); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 0, const char *), + "A/B-copied"); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 1, const char *), + "A/B-moved"); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 2, const char *), + "A1/B-copied-from-A"); + + /* Move target for "A/B-copied" (selected by default) is not ambiguous. */ + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/B-copied")); + + /* Move target for "A/mu-moved" is not ambiguous. */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1, + ctx, b->pool)); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/B-moved")); + + /* Select move target "A1/mu-copied-from-A". */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 2, + ctx, b->pool)); + + /* + * Possible working copy destinations for moved-away 'A/mu' are: + * (1): 'A/B-copied-from-A' + * (2): 'A1/B-copied-from-A' + */ + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + &possible_moved_to_abspaths, option, b->pool, b->pool)); + SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 2); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *), + sbox_wc_path(b, "A/B-copied-from-A")); + SVN_TEST_STRING_ASSERT( + APR_ARRAY_IDX(possible_moved_to_abspaths, 1, const char *), + sbox_wc_path(b, "A1/B-copied-from-A")); + + /* Select move target "A/B-moved". */ + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1, + ctx, b->pool)); + + /* Resolve the tree conflict. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, + svn_client_conflict_option_local_move_dir_merge, ctx, + b->pool)); + + /* The node "A/mu" should no longer exist. */ + SVN_TEST_ASSERT_ERROR(svn_client_conflict_get(&conflict, + sbox_wc_path(b, "A/B"), + ctx, pool, pool), + SVN_ERR_WC_PATH_NOT_FOUND); + + /* Ensure that the merged file has the expected status. */ + opt_rev.kind = svn_opt_revision_working; + sb.result_pool = b->pool; + SVN_ERR(svn_client_status6(NULL, ctx, sbox_wc_path(b, "A/B-moved/lambda"), + &opt_rev, svn_depth_unknown, TRUE, TRUE, + TRUE, TRUE, FALSE, TRUE, NULL, + status_func, &sb, b->pool)); + status = sb.status; + SVN_TEST_ASSERT(status->kind == svn_node_file); + SVN_TEST_ASSERT(status->versioned); + SVN_TEST_ASSERT(!status->conflicted); + SVN_TEST_ASSERT(status->node_status == svn_wc_status_modified); + SVN_TEST_ASSERT(status->text_status == svn_wc_status_modified); + SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none); + SVN_TEST_ASSERT(!status->copied); + SVN_TEST_ASSERT(!status->switched); + SVN_TEST_ASSERT(!status->file_external); + SVN_TEST_ASSERT(status->moved_from_abspath == NULL); + SVN_TEST_ASSERT(status->moved_to_abspath == NULL); + + /* And it should have expected contents. */ + SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, "A/B-moved/lambda"), + pool)); + SVN_TEST_STRING_ASSERT(buf->data, "Modified content." APR_EOL_STR); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_file_vs_dir_move_merge_assertion_failure(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b)); + svn_opt_revision_t opt_rev, peg_rev; + svn_client_ctx_t *ctx; + svn_client_conflict_t *conflict; + apr_array_header_t *options; + svn_client_conflict_option_t *option; + const char *wc_path; + + SVN_ERR(svn_test__sandbox_create(b, + "file_vs_dir_move_merge_assertion_failure", + opts, pool)); + + SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */ + + /* Create a copy of node "A" (the "trunk") to "A1" (the "branch"). */ + SVN_ERR(sbox_wc_copy(b, "A", "A1")); + SVN_ERR(sbox_wc_commit(b, "")); /* r2 */ + + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + /* Move and modify file on the "branch" */ + SVN_ERR(sbox_wc_move(b, "A1/B/lambda", "A1/B/lambda-moved")); + SVN_ERR(sbox_file_write(b, "A1/B/lambda-moved", + "Modified content." APR_EOL_STR)); + SVN_ERR(sbox_wc_commit(b, "")); /* r3 */ + + /* Move a directory and modify a file inside of it on the "trunk". */ + SVN_ERR(sbox_wc_move(b, "A/B", "A/B-moved")); + SVN_ERR(sbox_file_write(b, "A/B-moved/lambda", + "Modified content." APR_EOL_STR)); + SVN_ERR(sbox_wc_commit(b, "")); /* r4 */ + SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM)); + + /* Create a fresh working copy for "A1" ("branch"). */ + wc_path = svn_test_data_path("file_vs_dir_move_merge_assertion_failure2", + pool); + SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool)); + SVN_ERR(svn_io_make_dir_recursively(wc_path, pool)); + svn_test_add_dir_cleanup(wc_path); + + /* Merge "A" ("trunk") into a fresh working copy of "A1" ("branch"). */ + opt_rev.kind = svn_opt_revision_head; + opt_rev.value.number = SVN_INVALID_REVNUM; + peg_rev.kind = svn_opt_revision_unspecified; + SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool)); + SVN_ERR(svn_client_checkout3(NULL, svn_path_url_add_component2(b->repos_url, + "A1", pool), + wc_path, &peg_rev, &opt_rev, svn_depth_infinity, + TRUE, FALSE, ctx, pool)); + + SVN_ERR(svn_client_merge_peg5(svn_path_url_add_component2(b->repos_url, "A", + pool), + NULL, &opt_rev, wc_path, svn_depth_infinity, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + NULL, ctx, pool)); + + SVN_ERR(svn_client_conflict_get(&conflict, + svn_dirent_join(wc_path, "B", b->pool), + ctx, b->pool, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_delete_ignore, + svn_client_conflict_option_incoming_delete_accept, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + svn_client_conflict_option_incoming_move_dir_merge, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict, + ctx, b->pool, + b->pool)); + option = svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_incoming_move_dir_merge); + SVN_TEST_ASSERT(option != NULL); + + /* Resolve this conflict. Another one will be raised. */ + SVN_ERR(svn_client_conflict_tree_resolve_by_id( + conflict, + svn_client_conflict_option_incoming_move_dir_merge, ctx, + b->pool)); + + SVN_ERR(svn_client_conflict_get(&conflict, + svn_dirent_join_many(b->pool, + wc_path, "B-moved", "lambda", NULL), + ctx, b->pool, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + /* This used to trigger an assertion failure: + * svn_tests: E235000: In file 'subversion/libsvn_client/conflicts.c' \ + * line 2242: assertion failed (start_rev > end_rev) */ + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool)); + { + svn_client_conflict_option_id_t expected_opts[] = { + svn_client_conflict_option_postpone, + svn_client_conflict_option_accept_current_wc_state, + -1 /* end of list */ + }; + SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts, + b->pool)); + } + + /* Subversion is not yet smart enough to resolve this tree conflict. */ + + return SVN_NO_ERROR; +} + /* ========================================================================== */ @@ -5265,10 +6275,24 @@ static struct svn_test_descriptor_t test_funcs[] = "merge incoming move file merge with CRLF eols"), SVN_TEST_OPTS_PASS(test_merge_incoming_move_file_text_merge_native_eol, "merge incoming move file merge with native eols"), - SVN_TEST_OPTS_XFAIL(test_cherry_pick_post_move_edit, + SVN_TEST_OPTS_PASS(test_cherry_pick_post_move_edit, "cherry-pick edit from moved file"), SVN_TEST_OPTS_PASS(test_merge_incoming_move_dir_across_branches, - "merge incoming dir move across branches"), + "merge incoming dir move across branches"), + SVN_TEST_OPTS_PASS(test_update_incoming_delete_locally_deleted_file, + "update incoming delete to deleted file (#4739)"), + SVN_TEST_OPTS_PASS(test_merge_two_added_dirs_assertion_failure, + "merge two added dirs assertion failure (#4744)"), + SVN_TEST_OPTS_PASS(test_merge_incoming_delete_file_unrelated_move, + "do not suggest unrelated move targets (#4766)"), + SVN_TEST_OPTS_PASS(test_cherry_pick_post_move_edit_dir, + "cherry-pick edit from moved directory"), + SVN_TEST_OPTS_PASS(test_local_missing_abiguous_moves, + "local missing conflict with ambiguous moves"), + SVN_TEST_OPTS_PASS(test_local_missing_abiguous_moves_dir, + "local missing conflict with ambiguous dir moves"), + SVN_TEST_OPTS_PASS(test_file_vs_dir_move_merge_assertion_failure, + "file v dir move merge assertion failure"), SVN_TEST_NULL }; diff --git a/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c b/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c index 7a6e910..469eb4e 100644 --- a/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c +++ b/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c @@ -1631,8 +1631,8 @@ delta_chain_with_plain(const svn_test_opts_t *opts, svn_hash_sets(props, "p", svn_string_create(prop_value->data, pool)); hash_rep = svn_stringbuf_create_empty(pool); - svn_hash_write2(props, svn_stream_from_stringbuf(hash_rep, pool), "END", - pool); + SVN_ERR(svn_hash_write2(props, svn_stream_from_stringbuf(hash_rep, pool), + "END", pool)); SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); SVN_ERR(svn_fs_txn_root(&root, txn, pool)); diff --git a/subversion/tests/libsvn_repos/authz-test.c b/subversion/tests/libsvn_repos/authz-test.c index 6ee2448..1f6a4dd 100644 --- a/subversion/tests/libsvn_repos/authz-test.c +++ b/subversion/tests/libsvn_repos/authz-test.c @@ -277,9 +277,9 @@ test_authz_parse(const svn_test_opts_t *opts, printf("[users]\n"); if (authz->has_anon_rights) - print_user_rights(NULL, NULL, 0, &authz->anon_rights, pool); + SVN_ERR(print_user_rights(NULL, NULL, 0, &authz->anon_rights, pool)); if (authz->has_authn_rights) - print_user_rights(NULL, NULL, 0, &authz->authn_rights, pool); + SVN_ERR(print_user_rights(NULL, NULL, 0, &authz->authn_rights, pool)); SVN_ERR(svn_iter_apr_hash(NULL, authz->user_rights, print_user_rights, NULL, pool)); printf("\n\n"); @@ -478,6 +478,39 @@ issue_4741_groups(apr_pool_t *pool) return SVN_NO_ERROR; } +static svn_error_t * +reposful_reposless_stanzas_inherit(apr_pool_t *pool) +{ + const char rules[] = + "[groups]" NL + "company = user1, user2, user3" NL + "customer = customer1, customer2" NL + "" NL + "# company can read-write on everything" NL + "[/]" NL + "@company = rw" NL + "" NL + "[project1:/]" NL + "@customer = r" NL + "" NL + "[project2:/]" NL; + + svn_stringbuf_t *buf = svn_stringbuf_create(rules, pool); + svn_stream_t *stream = svn_stream_from_stringbuf(buf, pool); + svn_authz_t *authz; + svn_boolean_t access_granted; + + SVN_ERR(svn_repos_authz_parse(&authz, stream, NULL, pool)); + + SVN_ERR(svn_repos_authz_check_access(authz, "project1", "/foo", "user1", + svn_authz_write | svn_authz_recursive, + &access_granted, + pool)); + SVN_TEST_ASSERT(access_granted == TRUE); + + return SVN_NO_ERROR; +} + static int max_threads = 4; static struct svn_test_descriptor_t test_funcs[] = @@ -489,6 +522,8 @@ static struct svn_test_descriptor_t test_funcs[] = "test svn_authz__get_global_rights"), SVN_TEST_PASS2(issue_4741_groups, "issue 4741 groups"), + SVN_TEST_XFAIL2(reposful_reposless_stanzas_inherit, + "[foo:/] inherits [/]"), SVN_TEST_NULL }; diff --git a/subversion/tests/libsvn_repos/dump-load-test.c b/subversion/tests/libsvn_repos/dump-load-test.c index 6940c85..e7450df 100644 --- a/subversion/tests/libsvn_repos/dump-load-test.c +++ b/subversion/tests/libsvn_repos/dump-load-test.c @@ -81,7 +81,7 @@ test_dump_bad_props(svn_stringbuf_t **dump_data_p, notify_func, notify_baton, NULL, NULL, NULL, NULL, pool)); - svn_stream_close(stream); + SVN_ERR(svn_stream_close(stream)); /* Check that the property appears in the dump data */ expected_str = apr_psprintf(pool, "K %d\n%s\n" @@ -131,7 +131,7 @@ test_load_bad_props(svn_stringbuf_t *dump_data, notify_func, notify_baton, NULL, NULL, /*cancellation*/ pool)); - svn_stream_close(stream); + SVN_ERR(svn_stream_close(stream)); /* Check the loaded property */ fs = svn_repos_fs(repos); diff --git a/subversion/tests/libsvn_repos/repos-test.c b/subversion/tests/libsvn_repos/repos-test.c index 8d5472c..63b76e1 100644 --- a/subversion/tests/libsvn_repos/repos-test.c +++ b/subversion/tests/libsvn_repos/repos-test.c @@ -4461,7 +4461,7 @@ test_list(const svn_test_opts_t *opts, SVN_ERR(svn_repos_list(rev_root, "/A", patterns, svn_depth_infinity, FALSE, NULL, NULL, list_callback, &counter, NULL, NULL, pool)); - SVN_TEST_ASSERT(counter == 6); + SVN_TEST_ASSERT(counter == 7); return SVN_NO_ERROR; } diff --git a/subversion/tests/libsvn_subr/priority-queue-test.c b/subversion/tests/libsvn_subr/priority-queue-test.c index bd2d991..3fc5791 100644 --- a/subversion/tests/libsvn_subr/priority-queue-test.c +++ b/subversion/tests/libsvn_subr/priority-queue-test.c @@ -125,7 +125,7 @@ verify_queue_order(svn_priority_queue__t *queue) } /* the queue should now be empty */ - verify_empty_queue(queue); + SVN_ERR(verify_empty_queue(queue)); return SVN_NO_ERROR; } @@ -154,7 +154,7 @@ test_empty_queue(apr_pool_t *pool) svn_priority_queue__t *queue = svn_priority_queue__create(elements, compare_func); - verify_empty_queue(queue); + SVN_ERR(verify_empty_queue(queue)); return SVN_NO_ERROR; } @@ -214,7 +214,7 @@ test_update(apr_pool_t *pool) } /* the queue should now be empty */ - verify_empty_queue(queue); + SVN_ERR(verify_empty_queue(queue)); return SVN_NO_ERROR; } diff --git a/subversion/tests/libsvn_wc/conflict-data-test.c b/subversion/tests/libsvn_wc/conflict-data-test.c index 7d89825..239c18f 100644 --- a/subversion/tests/libsvn_wc/conflict-data-test.c +++ b/subversion/tests/libsvn_wc/conflict-data-test.c @@ -594,6 +594,7 @@ test_serialize_tree_conflict(const svn_test_opts_t *opts, svn_wc_conflict_reason_moved_away, svn_wc_conflict_action_delete, sbox_wc_path(&sbox, "A/B"), + sbox_wc_path(&sbox, "A/C"), pool, pool)); SVN_ERR(svn_wc__conflict_skel_set_op_switch( @@ -610,11 +611,13 @@ test_serialize_tree_conflict(const svn_test_opts_t *opts, { svn_wc_conflict_reason_t reason; svn_wc_conflict_action_t action; - const char *moved_away_op_root_abspath; + const char *moved_away_src_op_root_abspath; + const char *moved_away_dst_op_root_abspath; SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, - &moved_away_op_root_abspath, + &moved_away_src_op_root_abspath, + &moved_away_dst_op_root_abspath, sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, @@ -622,8 +625,10 @@ test_serialize_tree_conflict(const svn_test_opts_t *opts, SVN_TEST_ASSERT(reason == svn_wc_conflict_reason_moved_away); SVN_TEST_ASSERT(action == svn_wc_conflict_action_delete); - SVN_TEST_STRING_ASSERT(moved_away_op_root_abspath, + SVN_TEST_STRING_ASSERT(moved_away_src_op_root_abspath, sbox_wc_path(&sbox, "A/B")); + SVN_TEST_STRING_ASSERT(moved_away_dst_op_root_abspath, + sbox_wc_path(&sbox, "A/C")); } return SVN_NO_ERROR; diff --git a/subversion/tests/libsvn_wc/op-depth-test.c b/subversion/tests/libsvn_wc/op-depth-test.c index bf6592d..66e5928 100644 --- a/subversion/tests/libsvn_wc/op-depth-test.c +++ b/subversion/tests/libsvn_wc/op-depth-test.c @@ -535,7 +535,7 @@ check_db_conflicts(svn_test__sandbox_t *b, SVN_ERR(svn_wc__conflict_read_tree_conflict(&info->tc.reason, &info->tc.action, &move_src_abspath, - b->wc_ctx->db, + NULL, b->wc_ctx->db, local_abspath, conflict, b->pool, iterpool)); diff --git a/subversion/tests/libsvn_wc/utils.c b/subversion/tests/libsvn_wc/utils.c index 766f93d..51a0729 100644 --- a/subversion/tests/libsvn_wc/utils.c +++ b/subversion/tests/libsvn_wc/utils.c @@ -417,11 +417,12 @@ sbox_wc_revert(svn_test__sandbox_t *b, const char *path, svn_depth_t depth) SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, b->wc_ctx, dir_abspath, FALSE /* lock_anchor */, b->pool, b->pool)); - SVN_ERR(svn_wc_revert5(b->wc_ctx, abspath, depth, + SVN_ERR(svn_wc_revert6(b->wc_ctx, abspath, depth, FALSE /* use_commit_times */, NULL /* changelist_filter */, FALSE /* clear_changelists */, FALSE /* metadata_only */, + TRUE /*added_keep_local*/, NULL, NULL, /* cancel baton + func */ NULL, NULL, /* notify baton + func */ b->pool)); diff --git a/subversion/tests/libsvn_wc/wc-queries-test.c b/subversion/tests/libsvn_wc/wc-queries-test.c index 0a828c5..650f363 100644 --- a/subversion/tests/libsvn_wc/wc-queries-test.c +++ b/subversion/tests/libsvn_wc/wc-queries-test.c @@ -99,6 +99,7 @@ static const int slow_statements[] = STMT_SELECT_DELETE_LIST, STMT_SELECT_UPDATE_MOVE_LIST, STMT_FIND_REPOS_PATH_IN_WC, + STMT_SELECT_PRESENT_HIGHEST_WORKING_NODES_BY_BASENAME_AND_KIND, /* Designed as slow to avoid penalty on other queries */ STMT_SELECT_UNREFERENCED_PRISTINES, diff --git a/subversion/tests/libsvn_wc/wc-test-queries.h b/subversion/tests/libsvn_wc/wc-test-queries.h index b454af3..0a58a37 100644 --- a/subversion/tests/libsvn_wc/wc-test-queries.h +++ b/subversion/tests/libsvn_wc/wc-test-queries.h @@ -1,4 +1,4 @@ -/* This file is automatically generated from wc-test-queries.sql and subversion/tests/libsvn_wc/token-map.h. +/* This file is automatically generated from wc-test-queries.sql and token-map.h. * Do not edit this file -- edit the source and rerun gen-make.py */ #define STMT_SELECT_NODES_INFO 0 diff --git a/subversion/tests/svn_test_main.c b/subversion/tests/svn_test_main.c index c3537d3..31d670f 100644 --- a/subversion/tests/svn_test_main.c +++ b/subversion/tests/svn_test_main.c @@ -903,9 +903,12 @@ svn_test_main(int argc, const char *argv[], int max_threads, apr_err = apr_getopt_long(os, cl_options, &opt_id, &opt_arg); if (APR_STATUS_IS_EOF(apr_err)) break; - else if (apr_err && (apr_err != APR_BADCH)) + else if (apr_err) { /* Ignore invalid option error to allow passing arbitrary options */ + if (apr_err == APR_BADCH) + continue; + fprintf(stderr, "apr_getopt_long failed : [%d] %s\n", apr_err, apr_strerror(apr_err, errmsg, sizeof(errmsg))); exit(1); |