Hi, Bert. On trunk (released versions are unaffected), "svnsync sync" with an empty source repo fails an assertion in svn_ra_replay_range():
subversion/libsvn_ra/ra_loader.c' line 1198: assertion failed (SVN_IS_VALID_REVNUM(start_revision) && SVN_IS_VALID_REVNUM(end_revision) && start_revision <= end_revision && SVN_IS_VALID_REVNUM(low_water_mark)) because start_revision is 1 and end_revision is 0. The assertion was added in r1665480 <http://svn.apache.org/viewvc?revision=1665480&view=revision>, with the log msg mentioning "Add assertions here that already apply to some ra layers." One possible fix is to avoid calling it with an empty revision range, like this: Index: subversion/svnsync/svnsync.c =================================================================== --- subversion/svnsync/svnsync.c (revision 1701278) +++ subversion/svnsync/svnsync.c (working copy) @@ -1551,3 +1551,3 @@ do_synchronize(svn_ra_session_t *to_sess - if (from_latest < last_merged) + if (from_latest <= last_merged) return SVN_NO_ERROR; That makes the caller return early when there are no revisions to sync. The only other real caller (svnrdump) already uses a similar condition. However, I think we need to continue allowing the historical usage of svn_ra_replay_range() for backward compatibility. Should the assertion should be changed like this? - && start_revision <= end_revision + && start_revision <= (end_revision + 1) I haven't yet seen where the problem or restriction existed in some RA layers, so I don't know if that's a good enough fix on its own. My new svnsync test (empty repo) passes over all RA layers with this change. The attached patch contains both possible fixes, a log msg and a regression test. (I found this by running tests with the "--dump-load-cross-check" option, which I have not done for a while. Doing so also reports several other test failures, mostly just because those tests use deliberately invalid data.) - Julian
Fix a crash in 'svnsync sync' when the source repository is empty. ### This patch currently contains two possible fixes. See the email discussion about which one or ones we want. * subversion/libsvn_ra/ra_loader.c (svn_ra_replay_range): Relax the assertion to allow an empty range. * subversion/svnsync/svnsync.c (do_synchronize): Return earlier if there are no new revisions to synchronize. * subversion/tests/cmdline/svnsync_tests_data/empty-repository.dump New file. * subversion/tests/cmdline/svnsync_tests.py (setup_and_sync, run_sync): Allow specifying the expected output. (empty_repository): New test. (run_test): Run it. --This line, and those below, will be ignored-- Index: subversion/libsvn_ra/ra_loader.c =================================================================== --- subversion/libsvn_ra/ra_loader.c (revision 1701278) +++ subversion/libsvn_ra/ra_loader.c (working copy) @@ -1191,13 +1191,13 @@ svn_ra_replay_range(svn_ra_session_t *se apr_pool_t *pool) { svn_error_t *err; SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_revision) && SVN_IS_VALID_REVNUM(end_revision) - && start_revision <= end_revision + && start_revision <= (end_revision + 1) && SVN_IS_VALID_REVNUM(low_water_mark)); err = session->vtable->replay_range(session, start_revision, end_revision, low_water_mark, text_deltas, revstart_func, revfinish_func, Index: subversion/svnsync/svnsync.c =================================================================== --- subversion/svnsync/svnsync.c (revision 1701278) +++ subversion/svnsync/svnsync.c (working copy) @@ -1546,13 +1546,13 @@ do_synchronize(svn_ra_session_t *to_sess to_latest, last_merged); } /* Now check to see if there are any revisions to copy. */ SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool)); - if (from_latest < last_merged) + if (from_latest <= last_merged) return SVN_NO_ERROR; /* Ok, so there are new revisions, iterate over them copying them into the destination repository. */ SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool)); Index: subversion/tests/cmdline/svnsync_tests_data/empty-repository.dump =================================================================== --- subversion/tests/cmdline/svnsync_tests_data/empty-repository.dump (revision 0) +++ subversion/tests/cmdline/svnsync_tests_data/empty-repository.dump (working copy) @@ -0,0 +1,14 @@ +SVN-fs-dump-format-version: 2 + +UUID: 6ad9f820-0205-0410-94a2-c8cf366bb2b3 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2005-11-07T23:36:48.095832Z +PROPS-END + Index: subversion/tests/cmdline/svnsync_tests.py =================================================================== --- subversion/tests/cmdline/svnsync_tests.py (revision 1701278) +++ subversion/tests/cmdline/svnsync_tests.py (working copy) @@ -102,13 +102,14 @@ def run_info(url, expected_output=AnyOut svntest.actions.run_and_verify_svnsync(expected_output, expected_error, "info", url) def setup_and_sync(sbox, dump_file_contents, subdir=None, bypass_prop_validation=False, source_prop_encoding=None, - is_src_ra_local=None, is_dest_ra_local=None): + is_src_ra_local=None, is_dest_ra_local=None, + expected_output_sync=AnyOutput): """Create a repository for SBOX, load it with DUMP_FILE_CONTENTS, then create a mirror repository and sync it with SBOX. If is_src_ra_local or is_dest_ra_local is True, then run_init, run_sync, and run_copy_revprops will use the file:// scheme for the source and destination URLs. Return the mirror sandbox.""" # Create the empty master repository. sbox.build(create_wc=False, empty=True) # Load the repository from DUMP_FILE_PATH. @@ -139,13 +140,14 @@ def setup_and_sync(sbox, dump_file_conte dest_repo_url = dest_sbox.repo_url if is_dest_ra_local: dest_repo_url = dest_sbox.file_protocol_repo_url() run_init(dest_repo_url, repo_url, source_prop_encoding) run_sync(dest_repo_url, repo_url, - source_prop_encoding=source_prop_encoding) + source_prop_encoding=source_prop_encoding, + expected_output=expected_output_sync) run_copy_revprops(dest_repo_url, repo_url, source_prop_encoding=source_prop_encoding) return dest_sbox def verify_mirror(dest_sbox, exp_dump_file_contents): @@ -168,13 +170,14 @@ def verify_mirror(dest_sbox, exp_dump_fi svntest.verify.compare_dump_files( "Dump files", "DUMP", 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, - is_src_ra_local=None, is_dest_ra_local=None): + is_src_ra_local=None, is_dest_ra_local=None, + expected_output_sync=AnyOutput): """Load a dump file, sync repositories, and compare contents with the original or another dump file.""" # This directory contains all the dump files svnsync_tests_dir = os.path.join(os.path.dirname(sys.argv[0]), @@ -184,13 +187,14 @@ or another dump file.""" master_dumpfile_contents = open(os.path.join(svnsync_tests_dir, dump_file_name), 'rb').readlines() dest_sbox = setup_and_sync(sbox, master_dumpfile_contents, subdir, bypass_prop_validation, source_prop_encoding, - is_src_ra_local, is_dest_ra_local) + is_src_ra_local, is_dest_ra_local, + expected_output_sync=expected_output_sync) # Compare the dump produced by the mirror repository with either the original # dump file (used to create the master repository) or another specified dump # file. if exp_dump_file_name: exp_dump_file_contents = open(os.path.join(svnsync_tests_dir, @@ -569,12 +573,18 @@ PROPS-END # Run the sync dest_sbox = setup_and_sync(sbox, dump_in, bypass_prop_validation=True) # Compare the dump produced by the mirror repository with expected verify_mirror(dest_sbox, dump_out) +#---------------------------------------------------------------------- + +def empty_repository(sbox): + "empty repository" + run_test(sbox, "empty-repository.dump", expected_output_sync=[]) + ######################################################################## # Run the tests # list all tests here, starting with None: @@ -606,12 +616,13 @@ test_list = [ None, delete_svn_props, commit_a_copy_of_root, descend_into_replace, delete_revprops, fd_leak_sync_from_serf_to_local, # calls setrlimit mergeinfo_contains_r0, + empty_repository, ] if __name__ == '__main__': svntest.main.run_tests(test_list) # NOTREACHED