| /* fs-test.c --- tests for the filesystem |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <apr_pools.h> |
| #include <apr_time.h> |
| #include <apr_md5.h> |
| |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_time.h" |
| #include "svn_fs.h" |
| #include "svn_path.h" |
| #include "svn_delta.h" |
| #include "svn_test.h" |
| |
| #include "../fs-helpers.h" |
| |
| #include "../../libsvn_fs/fs.h" |
| #include "../../libsvn_fs/dag.h" |
| #include "../../libsvn_fs/node-rev.h" |
| #include "../../libsvn_fs/rev-table.h" |
| #include "../../libsvn_fs/nodes-table.h" |
| #include "../../libsvn_fs/trail.h" |
| #include "../../libsvn_fs/id.h" |
| |
| #include "../../libsvn_delta/delta.h" |
| |
| #define SET_STR(ps, s) ((ps)->data = (s), (ps)->len = strlen(s)) |
| |
| |
| /*-----------------------------------------------------------------*/ |
| |
| /** The actual fs-tests called by `make check` **/ |
| |
| /* Create a filesystem. */ |
| static svn_error_t * |
| create_berkeley_filesystem (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| |
| *msg = "svn_fs_create_berkeley"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create and close a repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-create-berkeley", pool)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Generic Berkeley DB error handler function. */ |
| static void |
| berkeley_error_handler (const char *errpfx, |
| char *msg) |
| { |
| fprintf (stderr, "%s%s\n", errpfx ? errpfx : "", msg); |
| } |
| |
| |
| /* Helper: commit TXN, expecting either success or failure: |
| * |
| * If EXPECTED_CONFLICT is null, then the commit is expected to |
| * succeed. If it does succeed, set *NEW_REV to the new revision; |
| * else return error. |
| * |
| * If EXPECTED_CONFLICT is non-null, it is either the empty string or |
| * the expected path of the conflict. If it is the empty string, any |
| * conflict is acceptable. If it is a non-empty string, the commit |
| * must fail due to conflict, and the conflict path must match |
| * EXPECTED_CONFLICT. If they don't match, return error. |
| * |
| * If a conflict is expected but the commit succeeds anyway, return |
| * error. |
| */ |
| static svn_error_t * |
| test_commit_txn (svn_revnum_t *new_rev, |
| svn_fs_txn_t *txn, |
| const char *expected_conflict, |
| apr_pool_t *pool) |
| { |
| const char *conflict; |
| svn_error_t *err; |
| |
| err = svn_fs_commit_txn (&conflict, new_rev, txn); |
| |
| if (err && (err->apr_err == SVN_ERR_FS_CONFLICT)) |
| { |
| if (! expected_conflict) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_CONFLICT, 0, NULL, pool, |
| "commit conflicted at `%s', but no conflict expected", |
| conflict ? conflict : "(missing conflict info!)"); |
| } |
| else if (conflict == NULL) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_CONFLICT, 0, NULL, pool, |
| "commit conflicted as expected, " |
| "but no conflict path was returned (`%s' expected)", |
| expected_conflict); |
| } |
| else if ((strcmp (expected_conflict, "") != 0) |
| && (strcmp (conflict, expected_conflict) != 0)) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_CONFLICT, 0, NULL, pool, |
| "commit conflicted at `%s', but expected conflict at `%s')", |
| conflict, expected_conflict); |
| } |
| } |
| else if (err) /* commit failed, but not due to conflict */ |
| { |
| return svn_error_quick_wrap |
| (err, "commit failed due to something other than a conflict"); |
| } |
| else /* err == NULL, so commit succeeded */ |
| { |
| if (expected_conflict) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "commit succeeded that was expected to fail at `%s'", |
| expected_conflict); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Open an existing filesystem. */ |
| static svn_error_t * |
| open_berkeley_filesystem (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs, *fs2; |
| |
| *msg = "open an existing Berkeley DB filesystem"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create and close a repository (using fs). */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-open-berkeley", pool)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| /* Create a different fs object, and use it to re-open the |
| repository again. */ |
| SVN_ERR (svn_test__fs_new (&fs2, pool)); |
| SVN_ERR (svn_fs_open_berkeley (fs2, "test-repo-open-berkeley")); |
| |
| /* Provide a handler for Berkeley DB error messages. */ |
| SVN_ERR (svn_fs_set_berkeley_errcall (fs2, berkeley_error_handler)); |
| |
| SVN_ERR (svn_fs_close_fs (fs2)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Begin a txn, check its name, then close it */ |
| static svn_error_t * |
| trivial_transaction (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| const char *txn_name; |
| |
| *msg = "begin a txn, check its name, then close it"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-trivial-txn", pool)); |
| |
| /* Begin a new transaction that is based on revision 0. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| |
| /* Test that the txn name is non-null. */ |
| SVN_ERR (svn_fs_txn_name (&txn_name, txn, pool)); |
| |
| if (! txn_name) |
| return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "Got a NULL txn name."); |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Open an existing transaction by name. */ |
| static svn_error_t * |
| reopen_trivial_transaction (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| const char *txn_name; |
| |
| *msg = "open an existing transaction by name"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-reopen-trivial-txn", pool)); |
| |
| /* Begin a new transaction that is based on revision 0. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_name (&txn_name, txn, pool)); |
| |
| /* Close the transaction. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Reopen the transaction by name */ |
| SVN_ERR (svn_fs_open_txn (&txn, fs, txn_name, pool)); |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Create a file! */ |
| static svn_error_t * |
| create_file_transaction (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| |
| *msg = "begin a txn, get the txn root, and add a file"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-create-file-txn", pool)); |
| |
| /* Begin a new transaction that is based on revision 0. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| |
| /* Get the txn root */ |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create a new file in the root directory. */ |
| SVN_ERR (svn_fs_make_file (txn_root, "beer.txt", pool)); |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| check_no_fs_error (svn_error_t *err, apr_pool_t *pool) |
| { |
| if (err && (err->apr_err != SVN_ERR_FS_NOT_OPEN)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "checking not opened filesystem got wrong error"); |
| else if (! err) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "checking not opened filesytem failed to get error"); |
| else |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Call functions with not yet opened filesystem and see it returns |
| correct error. */ |
| static svn_error_t * |
| call_functions_with_unopened_fs (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err; |
| svn_fs_t *fs = svn_fs_new (pool); |
| |
| *msg = "call functions with unopened filesystem and check errors"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* This is the exception --- it is perfectly okay to call |
| svn_fs_close_fs on an unopened filesystem. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| fs = svn_fs_new (pool); |
| err = svn_fs_set_berkeley_errcall (fs, berkeley_error_handler); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| |
| { |
| svn_fs_txn_t *ignored; |
| err = svn_fs_begin_txn (&ignored, fs, 0, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| err = svn_fs_open_txn (&ignored, fs, "0", pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| { |
| char **ignored; |
| err = svn_fs_list_transactions (&ignored, fs, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| { |
| svn_fs_root_t *ignored; |
| err = svn_fs_revision_root (&ignored, fs, 0, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| { |
| svn_revnum_t ignored; |
| err = svn_fs_youngest_rev (&ignored, fs, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| { |
| svn_string_t *ignored; |
| err = svn_fs_revision_prop (&ignored, fs, 0, NULL, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| { |
| apr_hash_t *ignored; |
| err = svn_fs_revision_proplist (&ignored, fs, 0, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| { |
| svn_string_t unused1; |
| err = svn_fs_change_rev_prop (fs, 0, NULL, &unused1, pool); |
| SVN_ERR (check_no_fs_error (err, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Make sure we get txn lists correctly. */ |
| static svn_error_t * |
| verify_txn_list (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn1, *txn2; |
| const char *name1, *name2; |
| char **txn_list; |
| |
| *msg = "create 2 txns, list them, and verify the list."; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-verify-txn-list", pool)); |
| |
| /* Begin a new transaction, get its name, close it. */ |
| SVN_ERR (svn_fs_begin_txn (&txn1, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_name (&name1, txn1, pool)); |
| SVN_ERR (svn_fs_close_txn (txn1)); |
| |
| /* Begin *another* transaction, get its name, close it. */ |
| SVN_ERR (svn_fs_begin_txn (&txn2, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_name (&name2, txn2, pool)); |
| SVN_ERR (svn_fs_close_txn (txn2)); |
| |
| /* Get the list of active transactions from the fs. */ |
| SVN_ERR (svn_fs_list_transactions (&txn_list, fs, pool)); |
| |
| /* Check the list. It should have *exactly* two entries. */ |
| if ((txn_list[0] == NULL) |
| || (txn_list[1] == NULL) |
| || (txn_list[2] != NULL)) |
| goto all_bad; |
| |
| /* We should be able to find our 2 txn names in the list, in some |
| order. */ |
| if ((! strcmp (txn_list[0], name1)) |
| && (! strcmp (txn_list[1], name2))) |
| goto all_good; |
| |
| else if ((! strcmp (txn_list[1], name1)) |
| && (! strcmp (txn_list[0], name2))) |
| goto all_good; |
| |
| all_bad: |
| |
| return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "Got a bogus txn list."); |
| all_good: |
| |
| /* Close the fs. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Test writing & reading a file's contents. */ |
| static svn_error_t * |
| write_and_read_file (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_stream_t *rstream; |
| svn_stringbuf_t *rstring; |
| svn_stringbuf_t *wstring; |
| |
| *msg = "write and read a file's contents"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| wstring = svn_stringbuf_create ("Wicki wild, wicki wicki wild.", pool); |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-read-and-write-file", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Add an empty file. */ |
| SVN_ERR (svn_fs_make_file (txn_root, "beer.txt", pool)); |
| |
| /* And write some data into this file. */ |
| SVN_ERR (svn_test__set_file_contents (txn_root, "beer.txt", |
| wstring->data, pool)); |
| |
| /* Now let's read the data back from the file. */ |
| SVN_ERR (svn_fs_file_contents (&rstream, txn_root, "beer.txt", pool)); |
| SVN_ERR (svn_test__stream_to_string (&rstring, rstream, pool)); |
| |
| /* Compare what was read to what was written. */ |
| if (! svn_stringbuf_compare (rstring, wstring)) |
| return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "data read != data written."); |
| |
| /* Clean up the repos. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Create a file, a directory, and a file in that directory! */ |
| static svn_error_t * |
| create_mini_tree_transaction (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| |
| *msg = "make a file, a subdir, and another file in that subdir"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-create-mini-tree-txn", pool)); |
| |
| /* Begin a new transaction that is based on revision 0. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| |
| /* Get the txn root */ |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create a new file in the root directory. */ |
| SVN_ERR (svn_fs_make_file (txn_root, "wine.txt", pool)); |
| |
| /* Create a new directory in the root directory. */ |
| SVN_ERR (svn_fs_make_dir (txn_root, "keg", pool)); |
| |
| /* Now, create a file in our new directory. */ |
| SVN_ERR (svn_fs_make_file (txn_root, "keg/beer.txt", pool)); |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Create a file, a directory, and a file in that directory! */ |
| static svn_error_t * |
| create_greek_tree_transaction (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| |
| *msg = "make The Official Subversion Test Tree"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-create-greek-tree-txn", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create and verify the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Verify that entry KEY is present in ENTRIES, and that its value is |
| an svn_fs_dirent_t whose name and id are not null. */ |
| static svn_error_t * |
| verify_entry (apr_hash_t *entries, const char *key, apr_pool_t *pool) |
| { |
| svn_fs_dirent_t *ent = apr_hash_get (entries, key, |
| APR_HASH_KEY_STRING); |
| |
| if (ent == NULL) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "didn't find dir entry for \"%s\"", key); |
| |
| if ((ent->name == NULL) && (ent->id == NULL)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir entry for \"%s\" has null name and null id", key); |
| |
| if (ent->name == NULL) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir entry for \"%s\" has null name", key); |
| |
| if (ent->id == NULL) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir entry for \"%s\" has null id", key); |
| |
| if (strcmp (ent->name, key) != 0) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir entry for \"%s\" contains wrong name (\"%s\")", key, ent->name); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| list_directory (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| apr_hash_t *entries; |
| |
| *msg = "fill a directory, then list it"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-list-dir", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* We create this tree |
| * |
| * /q |
| * /A/x |
| * /A/y |
| * /A/z |
| * /B/m |
| * /B/n |
| * /B/o |
| * |
| * then list dir A. It should have 3 files: "x", "y", and "z", no |
| * more, no less. |
| */ |
| |
| /* Create the tree. */ |
| SVN_ERR (svn_fs_make_file (txn_root, "q", pool)); |
| SVN_ERR (svn_fs_make_dir (txn_root, "A", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/x", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/y", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/z", pool)); |
| SVN_ERR (svn_fs_make_dir (txn_root, "B", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "B/m", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "B/n", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "B/o", pool)); |
| |
| /* Get A's entries. */ |
| SVN_ERR (svn_fs_dir_entries (&entries, txn_root, "A", pool)); |
| |
| /* Make sure exactly the right set of entries is present. */ |
| if (apr_hash_count (entries) != 3) |
| { |
| return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unexpected number of entries in dir"); |
| } |
| else |
| { |
| SVN_ERR (verify_entry (entries, "x", pool)); |
| SVN_ERR (verify_entry (entries, "y", pool)); |
| SVN_ERR (verify_entry (entries, "z", pool)); |
| } |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| revision_props (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| apr_hash_t *proplist; |
| svn_string_t *value; |
| int i; |
| svn_string_t s1; |
| |
| const char *initial_props[4][2] = { |
| { "color", "red" }, |
| { "size", "XXL" }, |
| { "favorite saturday morning cartoon", "looney tunes" }, |
| { "auto", "Green 1997 Saturn SL1" } |
| }; |
| |
| const char *final_props[4][2] = { |
| { "color", "violet" }, |
| { "flower", "violet" }, |
| { "favorite saturday morning cartoon", "looney tunes" }, |
| { "auto", "Red 2000 Chevrolet Blazer" } |
| }; |
| |
| *msg = "set and get some revision properties"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Open the fs */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-rev-props", pool)); |
| |
| /* Set some properties on the revision. */ |
| for (i = 0; i < 4; i++) |
| { |
| SET_STR (&s1, initial_props[i][1]); |
| SVN_ERR (svn_fs_change_rev_prop (fs, 0, initial_props[i][0], &s1, pool)); |
| } |
| |
| /* Change some of the above properties. */ |
| SET_STR (&s1, "violet"); |
| SVN_ERR (svn_fs_change_rev_prop (fs, 0, "color", &s1, pool)); |
| |
| SET_STR (&s1, "Red 2000 Chevrolet Blazer"); |
| SVN_ERR (svn_fs_change_rev_prop (fs, 0, "auto", &s1, pool)); |
| |
| /* Remove a property altogether */ |
| SVN_ERR (svn_fs_change_rev_prop (fs, 0, "size", NULL, pool)); |
| |
| /* Copy a property's value into a new property. */ |
| SVN_ERR (svn_fs_revision_prop (&value, fs, 0, "color", pool)); |
| |
| s1.data = value->data; |
| s1.len = value->len; |
| SVN_ERR (svn_fs_change_rev_prop (fs, 0, "flower", &s1, pool)); |
| |
| /* Obtain a list of all current properties, and make sure it matches |
| the expected values. */ |
| SVN_ERR (svn_fs_revision_proplist (&proplist, fs, 0, pool)); |
| { |
| svn_string_t *prop_value; |
| |
| if (apr_hash_count (proplist) < 4 ) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "too few revision properties found"); |
| |
| /* Loop through our list of expected revision property name/value |
| pairs. */ |
| for (i = 0; i < 4; i++) |
| { |
| /* For each expected property: */ |
| |
| /* Step 1. Find it by name in the hash of all rev. props |
| returned to us by svn_fs_revision_proplist. If it can't be |
| found, return an error. */ |
| prop_value = apr_hash_get (proplist, |
| final_props[i][0], |
| APR_HASH_KEY_STRING); |
| if (! prop_value) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unable to find expected revision property"); |
| |
| /* Step 2. Make sure the value associated with it is the same |
| as what was expected, else return an error. */ |
| if (strcmp (prop_value->data, final_props[i][1])) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "revision property had an unexpected value"); |
| } |
| } |
| |
| /* Close the fs. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| transaction_props (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| apr_hash_t *proplist; |
| svn_string_t *value; |
| svn_revnum_t after_rev; |
| int i; |
| svn_string_t s1; |
| |
| const char *initial_props[4][2] = { |
| { "color", "red" }, |
| { "size", "XXL" }, |
| { "favorite saturday morning cartoon", "looney tunes" }, |
| { "auto", "Green 1997 Saturn SL1" } |
| }; |
| |
| const char *final_props[5][2] = { |
| { "color", "violet" }, |
| { "flower", "violet" }, |
| { "favorite saturday morning cartoon", "looney tunes" }, |
| { "auto", "Red 2000 Chevrolet Blazer" }, |
| { SVN_PROP_REVISION_DATE, "<some datestamp value>" } |
| }; |
| |
| *msg = "set/get txn props, commit, validate new rev props"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Open the fs */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-txn-props", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| |
| /* Set some properties on the revision. */ |
| for (i = 0; i < 4; i++) |
| { |
| SET_STR (&s1, initial_props[i][1]); |
| SVN_ERR (svn_fs_change_txn_prop (txn, initial_props[i][0], &s1, pool)); |
| } |
| |
| /* Change some of the above properties. */ |
| SET_STR (&s1, "violet"); |
| SVN_ERR (svn_fs_change_txn_prop (txn, "color", &s1, pool)); |
| |
| SET_STR (&s1, "Red 2000 Chevrolet Blazer"); |
| SVN_ERR (svn_fs_change_txn_prop (txn, "auto", &s1, pool)); |
| |
| /* Remove a property altogether */ |
| SVN_ERR (svn_fs_change_txn_prop (txn, "size", NULL, pool)); |
| |
| /* Copy a property's value into a new property. */ |
| SVN_ERR (svn_fs_txn_prop (&value, txn, "color", pool)); |
| |
| s1.data = value->data; |
| s1.len = value->len; |
| SVN_ERR (svn_fs_change_txn_prop (txn, "flower", &s1, pool)); |
| |
| /* Obtain a list of all current properties, and make sure it matches |
| the expected values. */ |
| SVN_ERR (svn_fs_txn_proplist (&proplist, txn, pool)); |
| { |
| svn_string_t *prop_value; |
| |
| /* All transactions get a datestamp property at their inception, |
| so we expect *5*, not 4 properties. */ |
| if (apr_hash_count (proplist) != 5 ) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unexpected number of transaction properties were found"); |
| |
| /* Loop through our list of expected revision property name/value |
| pairs. */ |
| for (i = 0; i < 5; i++) |
| { |
| /* For each expected property: */ |
| |
| /* Step 1. Find it by name in the hash of all rev. props |
| returned to us by svn_fs_revision_proplist. If it can't be |
| found, return an error. */ |
| prop_value = apr_hash_get (proplist, |
| final_props[i][0], |
| APR_HASH_KEY_STRING); |
| if (! prop_value) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unable to find expected transaction property"); |
| |
| /* Step 2. Make sure the value associated with it is the same |
| as what was expected, else return an error. */ |
| if (strcmp (final_props[i][0], SVN_PROP_REVISION_DATE)) |
| if (strcmp (prop_value->data, final_props[i][1])) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "transaction property had an unexpected value"); |
| } |
| } |
| |
| /* Commit (and close) the transaction. */ |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| if (after_rev != 1) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "committed transaction got wrong revision number"); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Obtain a list of all properties on the new revision, and make |
| sure it matches the expected values. If you're wondering, the |
| expected values should be the exact same set of properties that |
| existed on the transaction just prior to its being committed. */ |
| SVN_ERR (svn_fs_revision_proplist (&proplist, fs, after_rev, pool)); |
| { |
| svn_string_t *prop_value; |
| |
| if (apr_hash_count (proplist) < 5 ) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unexpected number of revision properties were found"); |
| |
| /* Loop through our list of expected revision property name/value |
| pairs. */ |
| for (i = 0; i < 5; i++) |
| { |
| /* For each expected property: */ |
| |
| /* Step 1. Find it by name in the hash of all rev. props |
| returned to us by svn_fs_revision_proplist. If it can't be |
| found, return an error. */ |
| prop_value = apr_hash_get (proplist, |
| final_props[i][0], |
| APR_HASH_KEY_STRING); |
| if (! prop_value) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unable to find expected revision property"); |
| |
| /* Step 2. Make sure the value associated with it is the same |
| as what was expected, else return an error. */ |
| if (strcmp (final_props[i][0], SVN_PROP_REVISION_DATE)) |
| if (strcmp (prop_value->data, final_props[i][1])) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "revision property had an unexpected value"); |
| } |
| } |
| |
| /* Close the fs. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| node_props (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| apr_hash_t *proplist; |
| svn_string_t *value; |
| int i; |
| svn_string_t s1; |
| |
| const char *initial_props[4][2] = { |
| { "Best Rock Artist", "Creed" }, |
| { "Best Rap Artist", "Eminem" }, |
| { "Best Country Artist", "(null)" }, |
| { "Best Sound Designer", "Pluessman" } |
| }; |
| |
| const char *final_props[4][2] = { |
| { "Best Rock Artist", "P.O.D." }, |
| { "Best Rap Artist", "Busta Rhymes" }, |
| { "Best Sound Designer", "Pluessman" }, |
| { "Biggest Cakewalk Fanatic", "Pluessman" } |
| }; |
| |
| *msg = "set and get some node properties"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Open the fs and transaction */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-node-props", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Make a node to put some properties into */ |
| SVN_ERR (svn_fs_make_file (txn_root, "music.txt", pool)); |
| |
| /* Set some properties on the nodes. */ |
| for (i = 0; i < 4; i++) |
| { |
| SET_STR (&s1, initial_props[i][1]); |
| SVN_ERR (svn_fs_change_node_prop |
| (txn_root, "music.txt", initial_props[i][0], &s1, pool)); |
| } |
| |
| /* Change some of the above properties. */ |
| SET_STR (&s1, "P.O.D."); |
| SVN_ERR (svn_fs_change_node_prop (txn_root, "music.txt", "Best Rock Artist", |
| &s1, pool)); |
| |
| SET_STR (&s1, "Busta Rhymes"); |
| SVN_ERR (svn_fs_change_node_prop (txn_root, "music.txt", "Best Rap Artist", |
| &s1, pool)); |
| |
| /* Remove a property altogether */ |
| SVN_ERR (svn_fs_change_node_prop (txn_root, "music.txt", |
| "Best Country Artist", NULL, pool)); |
| |
| /* Copy a property's value into a new property. */ |
| SVN_ERR (svn_fs_node_prop (&value, txn_root, "music.txt", |
| "Best Sound Designer", pool)); |
| |
| s1.data = value->data; |
| s1.len = value->len; |
| SVN_ERR (svn_fs_change_node_prop (txn_root, "music.txt", |
| "Biggest Cakewalk Fanatic", &s1, pool)); |
| |
| /* Obtain a list of all current properties, and make sure it matches |
| the expected values. */ |
| SVN_ERR (svn_fs_node_proplist (&proplist, txn_root, "music.txt", pool)); |
| { |
| svn_string_t *prop_value; |
| |
| if (apr_hash_count (proplist) != 4 ) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unexpected number of node properties were found"); |
| |
| /* Loop through our list of expected node property name/value |
| pairs. */ |
| for (i = 0; i < 4; i++) |
| { |
| /* For each expected property: */ |
| |
| /* Step 1. Find it by name in the hash of all node props |
| returned to us by svn_fs_node_proplist. If it can't be |
| found, return an error. */ |
| prop_value = apr_hash_get (proplist, |
| final_props[i][0], |
| APR_HASH_KEY_STRING); |
| if (! prop_value) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "unable to find expected node property"); |
| |
| /* Step 2. Make sure the value associated with it is the same |
| as what was expected, else return an error. */ |
| if (strcmp (prop_value->data, final_props[i][1])) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "node property had an unexpected value"); |
| } |
| } |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Set *PRESENT to true if entry NAME is present in directory PATH |
| under ROOT, else set *PRESENT to false. */ |
| static svn_error_t * |
| check_entry (svn_fs_root_t *root, |
| const char *path, |
| const char *name, |
| svn_boolean_t *present, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *entries; |
| svn_fs_dirent_t *ent; |
| |
| SVN_ERR (svn_fs_dir_entries (&entries, root, path, pool)); |
| ent = apr_hash_get (entries, name, APR_HASH_KEY_STRING); |
| |
| if (ent) |
| *present = TRUE; |
| else |
| *present = FALSE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Return an error if entry NAME is absent in directory PATH under ROOT. */ |
| static svn_error_t * |
| check_entry_present (svn_fs_root_t *root, const char *path, |
| const char *name, apr_pool_t *pool) |
| { |
| svn_boolean_t present; |
| SVN_ERR (check_entry (root, path, name, &present, pool)); |
| |
| if (! present) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "entry \"%s\" absent when it should be present", name); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Return an error if entry NAME is present in directory PATH under ROOT. */ |
| static svn_error_t * |
| check_entry_absent (svn_fs_root_t *root, const char *path, |
| const char *name, apr_pool_t *pool) |
| { |
| svn_boolean_t present; |
| SVN_ERR (check_entry (root, path, name, &present, pool)); |
| |
| if (present) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "entry \"%s\" present when it should be absent", name); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct check_id_args |
| { |
| svn_fs_t *fs; |
| svn_fs_id_t *id; |
| svn_boolean_t present; |
| }; |
| |
| |
| static svn_error_t * |
| txn_body_check_id (void *baton, trail_t *trail) |
| { |
| struct check_id_args *args = baton; |
| skel_t *noderev; |
| svn_error_t *err; |
| |
| err = svn_fs__get_node_revision (&noderev, args->fs, args->id, trail); |
| |
| if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND)) |
| args->present = FALSE; |
| else if (! err) |
| args->present = TRUE; |
| else |
| { |
| svn_stringbuf_t *id_str = svn_fs_unparse_id (args->id, trail->pool); |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, trail->pool, |
| "error looking for node revision id \"%s\"", id_str->data); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Set *PRESENT to true if node revision ID is present in filesystem |
| FS, else set *PRESENT to false. */ |
| static svn_error_t * |
| check_id (svn_fs_t *fs, svn_fs_id_t *id, svn_boolean_t *present, |
| apr_pool_t *pool) |
| { |
| struct check_id_args args; |
| |
| args.id = id; |
| args.fs = fs; |
| SVN_ERR (svn_fs__retry_txn (fs, txn_body_check_id, &args, pool)); |
| |
| if (args.present) |
| *present = TRUE; |
| else |
| *present = FALSE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Return error if node revision ID is not present in FS. */ |
| static svn_error_t * |
| check_id_present (svn_fs_t *fs, svn_fs_id_t *id, apr_pool_t *pool) |
| { |
| svn_boolean_t present; |
| SVN_ERR (check_id (fs, id, &present, pool)); |
| |
| if (! present) |
| { |
| svn_stringbuf_t *id_str = svn_fs_unparse_id (id, pool); |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "node revision id \"%s\" absent when should be present", |
| id_str->data); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Return error if node revision ID is present in FS. */ |
| static svn_error_t * |
| check_id_absent (svn_fs_t *fs, svn_fs_id_t *id, apr_pool_t *pool) |
| { |
| svn_boolean_t present; |
| SVN_ERR (check_id (fs, id, &present, pool)); |
| |
| if (present) |
| { |
| svn_stringbuf_t *id_str = svn_fs_unparse_id (id, pool); |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "node revision id \"%s\" present when should be absent", |
| id_str->data); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Test that aborting a Subversion transaction works. |
| |
| NOTE: This function tests internal filesystem interfaces, not just |
| the public filesystem interface. */ |
| static svn_error_t * |
| abort_txn (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn1, *txn2; |
| svn_fs_root_t *txn1_root, *txn2_root; |
| const char *txn1_name, *txn2_name; |
| |
| *msg = "abort a transaction"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare two txns to receive the Greek tree. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-abort-txn", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn1, fs, 0, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn2, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn1_root, txn1, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn2_root, txn2, pool)); |
| |
| /* Save their names for later. */ |
| SVN_ERR (svn_fs_txn_name (&txn1_name, txn1, pool)); |
| SVN_ERR (svn_fs_txn_name (&txn2_name, txn2, pool)); |
| |
| /* Create greek trees in them. */ |
| SVN_ERR (svn_test__create_greek_tree (txn1_root, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn2_root, pool)); |
| |
| /* The test is to abort txn2, while leaving txn1. |
| * |
| * After we abort txn2, we make sure that a) all of its nodes |
| * disappeared from the database, and b) none of txn1's nodes |
| * disappeared. |
| * |
| * Finally, we create a third txn, and check that the name it got is |
| * different from the names of txn1 and txn2. |
| */ |
| |
| { |
| /* Yes, I really am this paranoid. */ |
| |
| /* IDs for every file in the standard Greek Tree. */ |
| svn_fs_id_t |
| *t1_root_id, *t2_root_id, |
| *t1_iota_id, *t2_iota_id, |
| *t1_A_id, *t2_A_id, |
| *t1_mu_id, *t2_mu_id, |
| *t1_B_id, *t2_B_id, |
| *t1_lambda_id, *t2_lambda_id, |
| *t1_E_id, *t2_E_id, |
| *t1_alpha_id, *t2_alpha_id, |
| *t1_beta_id, *t2_beta_id, |
| *t1_F_id, *t2_F_id, |
| *t1_C_id, *t2_C_id, |
| *t1_D_id, *t2_D_id, |
| *t1_gamma_id, *t2_gamma_id, |
| *t1_H_id, *t2_H_id, |
| *t1_chi_id, *t2_chi_id, |
| *t1_psi_id, *t2_psi_id, |
| *t1_omega_id, *t2_omega_id, |
| *t1_G_id, *t2_G_id, |
| *t1_pi_id, *t2_pi_id, |
| *t1_rho_id, *t2_rho_id, |
| *t1_tau_id, *t2_tau_id; |
| |
| SVN_ERR (svn_fs_node_id (&t1_root_id, txn1_root, "", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_root_id, txn2_root, "", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_iota_id, txn1_root, "iota", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_iota_id, txn2_root, "iota", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_A_id, txn1_root, "/A", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_A_id, txn2_root, "/A", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_mu_id, txn1_root, "/A/mu", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_mu_id, txn2_root, "/A/mu", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_B_id, txn1_root, "/A/B", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_B_id, txn2_root, "/A/B", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_lambda_id, txn1_root, "/A/B/lambda", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_lambda_id, txn2_root, "/A/B/lambda", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_E_id, txn1_root, "/A/B/E", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_E_id, txn2_root, "/A/B/E", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_alpha_id, txn1_root, "/A/B/E/alpha", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_alpha_id, txn2_root, "/A/B/E/alpha", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_beta_id, txn1_root, "/A/B/E/beta", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_beta_id, txn2_root, "/A/B/E/beta", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_F_id, txn1_root, "/A/B/F", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_F_id, txn2_root, "/A/B/F", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_C_id, txn1_root, "/A/C", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_C_id, txn2_root, "/A/C", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_D_id, txn1_root, "/A/D", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_D_id, txn2_root, "/A/D", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_gamma_id, txn1_root, "/A/D/gamma", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_gamma_id, txn2_root, "/A/D/gamma", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_H_id, txn1_root, "/A/D/H", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_H_id, txn2_root, "/A/D/H", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_chi_id, txn1_root, "/A/D/H/chi", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_chi_id, txn2_root, "/A/D/H/chi", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_psi_id, txn1_root, "/A/D/H/psi", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_psi_id, txn2_root, "/A/D/H/psi", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_omega_id, txn1_root, "/A/D/H/omega", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_omega_id, txn2_root, "/A/D/H/omega", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_G_id, txn1_root, "/A/D/G", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_G_id, txn2_root, "/A/D/G", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_pi_id, txn1_root, "/A/D/G/pi", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_pi_id, txn2_root, "/A/D/G/pi", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_rho_id, txn1_root, "/A/D/G/rho", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_rho_id, txn2_root, "/A/D/G/rho", pool)); |
| SVN_ERR (svn_fs_node_id (&t1_tau_id, txn1_root, "/A/D/G/tau", pool)); |
| SVN_ERR (svn_fs_node_id (&t2_tau_id, txn2_root, "/A/D/G/tau", pool)); |
| |
| /* Abort just txn2. */ |
| SVN_ERR (svn_fs_abort_txn (txn2)); |
| |
| /* Now test that all the nodes in txn2 at the time of the abort |
| * are gone, but all of the ones in txn1 are still there. |
| */ |
| |
| /* Check that every node rev in t2 has vanished from the fs. */ |
| SVN_ERR (check_id_absent (fs, t2_root_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_iota_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_A_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_mu_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_B_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_lambda_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_E_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_alpha_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_beta_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_F_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_C_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_D_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_gamma_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_H_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_chi_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_psi_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_omega_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_G_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_pi_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_rho_id, pool)); |
| SVN_ERR (check_id_absent (fs, t2_tau_id, pool)); |
| |
| /* Check that every node rev in t1 is still in the fs. */ |
| SVN_ERR (check_id_present (fs, t1_root_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_iota_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_A_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_mu_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_B_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_lambda_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_E_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_alpha_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_beta_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_F_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_C_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_D_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_gamma_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_H_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_chi_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_psi_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_omega_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_G_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_pi_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_rho_id, pool)); |
| SVN_ERR (check_id_present (fs, t1_tau_id, pool)); |
| } |
| |
| /* Test that txn2 itself is gone, by trying to open it. */ |
| { |
| svn_fs_txn_t *txn2_again; |
| svn_error_t *err; |
| |
| err = svn_fs_open_txn (&txn2_again, fs, txn2_name, pool); |
| if (err && (err->apr_err != SVN_ERR_FS_NO_SUCH_TRANSACTION)) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "opening non-existent txn got wrong error"); |
| } |
| else if (! err) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "opening non-existent txn failed to get error"); |
| } |
| } |
| |
| /* Test that txn names are not recycled, by opening a new txn. */ |
| { |
| svn_fs_txn_t *txn3; |
| const char *txn3_name; |
| |
| SVN_ERR (svn_fs_begin_txn (&txn3, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_name (&txn3_name, txn3, pool)); |
| |
| if ((strcmp (txn3_name, txn2_name) == 0) |
| || (strcmp (txn3_name, txn1_name) == 0)) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "txn name \"%s\" was recycled", txn3_name); |
| } |
| |
| SVN_ERR (svn_fs_close_txn (txn3)); |
| } |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn1)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Fetch the youngest revision from a repos. */ |
| static svn_error_t * |
| fetch_youngest_rev (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_revnum_t new_rev; |
| svn_revnum_t youngest_rev, new_youngest_rev; |
| |
| *msg = "fetch the youngest revision from a filesystem"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-youngest-rev", pool)); |
| |
| /* Get youngest revision of brand spankin' new filesystem. */ |
| SVN_ERR (svn_fs_youngest_rev (&youngest_rev, fs, pool)); |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-commit-txn", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* Commit it. */ |
| SVN_ERR (test_commit_txn (&new_rev, txn, NULL, pool)); |
| |
| /* Get the new youngest revision. */ |
| SVN_ERR (svn_fs_youngest_rev (&new_youngest_rev, fs, pool)); |
| |
| if (youngest_rev == new_rev) |
| return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "commit didn't bump up revision number"); |
| |
| if (new_youngest_rev != new_rev) |
| return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "couldn't fetch youngest revision"); |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Test committing against an empty repository. |
| todo: also test committing against youngest? */ |
| static svn_error_t * |
| basic_commit (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *revision_root; |
| svn_revnum_t before_rev, after_rev; |
| const char *conflict; |
| |
| *msg = "basic commit"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-basic-commit", pool)); |
| |
| /* Save the current youngest revision. */ |
| SVN_ERR (svn_fs_youngest_rev (&before_rev, fs, pool)); |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Paranoidly check that the current youngest rev is unchanged. */ |
| SVN_ERR (svn_fs_youngest_rev (&after_rev, fs, pool)); |
| if (after_rev != before_rev) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "youngest revision changed unexpectedly"); |
| |
| /* Create the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* Commit it. */ |
| SVN_ERR (svn_fs_commit_txn (&conflict, &after_rev, txn)); |
| |
| /* Close the transaction */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Make sure it's a different revision than before. */ |
| if (after_rev == before_rev) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "youngest revision failed to change"); |
| |
| /* Get root of the revision */ |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| |
| /* Check the tree. */ |
| SVN_ERR (svn_test__check_greek_tree (revision_root, pool)); |
| |
| /* Close the fs. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| static svn_error_t * |
| test_tree_node_validation (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *revision_root; |
| svn_revnum_t after_rev; |
| const char *conflict; |
| |
| *msg = "testing tree validation helper"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-validate-tree-entries", pool)); |
| |
| /* In a txn, create the greek tree. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* Carefully validate that tree in the transaction. */ |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 20, pool)); |
| |
| /* Go ahead and commit the tree */ |
| SVN_ERR (svn_fs_commit_txn (&conflict, &after_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Carefully validate that tree in the new revision, now. */ |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, 20, pool)); |
| } |
| |
| /* In a new txn, modify the greek tree. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is a new version of 'iota'.\n" }, |
| { "A", 0 }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", "This is a new version of 'iota'.\n", pool)); |
| SVN_ERR (svn_fs_delete (txn_root, "A/mu", pool)); |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/G", pool)); |
| SVN_ERR (svn_fs_make_dir (txn_root, "A/D/I", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/I/delta", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/I/delta", "This is the file 'delta'.\n", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/I/epsilon", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/I/epsilon", "This is the file 'epsilon'.\n", |
| pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/C/kappa", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/C/kappa", "This is the file 'kappa'.\n", pool)); |
| |
| /* Carefully validate that tree in the transaction. */ |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 19, pool)); |
| |
| /* Go ahead and commit the tree */ |
| SVN_ERR (svn_fs_commit_txn (&conflict, &after_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Carefully validate that tree in the new revision, now. */ |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 19, pool)); |
| } |
| |
| /* Close the filesystem. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| fetch_by_id (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *revision_root, *id_root; |
| svn_revnum_t after_rev; |
| svn_error_t *err; |
| |
| *msg = "fetch by id"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Commit a Greek Tree as the first revision. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-fetch-by-id", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &after_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Get one root for the committed Greek Tree, one for the fs. */ |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_id_root (&id_root, fs, pool)); |
| |
| /* Get the IDs of some random paths, then fetch some content by ID. */ |
| { |
| svn_fs_id_t *iota_id, *beta_id, *C_id, *D_id, *omega_id; |
| svn_stringbuf_t *iota_str, *beta_str, *C_str, *D_str, *omega_str; |
| svn_stringbuf_t *not_an_id_str = svn_stringbuf_create ("fish", pool); |
| apr_hash_t *entries; |
| apr_off_t len; |
| void *val; |
| int is; |
| |
| SVN_ERR (svn_fs_node_id (&iota_id, revision_root, "iota", pool)); |
| SVN_ERR (svn_fs_node_id (&beta_id, revision_root, "A/B/E/beta", pool)); |
| SVN_ERR (svn_fs_node_id (&C_id, revision_root, "A/C", pool)); |
| SVN_ERR (svn_fs_node_id (&D_id, revision_root, "A/D", pool)); |
| SVN_ERR (svn_fs_node_id (&omega_id, revision_root, "A/D/H/omega", pool)); |
| |
| iota_str = svn_fs_unparse_id (iota_id, pool); |
| beta_str = svn_fs_unparse_id (beta_id, pool); |
| C_str = svn_fs_unparse_id (C_id, pool); |
| D_str = svn_fs_unparse_id (D_id, pool); |
| omega_str = svn_fs_unparse_id (omega_id, pool); |
| |
| /* Check iota. */ |
| SVN_ERR (svn_fs_is_dir (&is, id_root, iota_str->data, pool)); |
| if (is) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "file fetched by node claimed to be a directory"); |
| |
| SVN_ERR (svn_fs_is_file (&is, id_root, iota_str->data, pool)); |
| if (! is) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "file fetched by node claimed not to be a file"); |
| |
| SVN_ERR (svn_fs_is_different (&is, revision_root, "iota", |
| id_root, iota_str->data, pool)); |
| if (is) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "fetching file by path and by node got different results"); |
| |
| |
| /* Check D. */ |
| SVN_ERR (svn_fs_is_file (&is, id_root, D_str->data, pool)); |
| if (is) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir fetched by node claimed to be a file"); |
| |
| SVN_ERR (svn_fs_is_dir (&is, id_root, D_str->data, pool)); |
| if (! is) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir fetched by node claimed not to be a dir"); |
| |
| SVN_ERR (svn_fs_is_different (&is, revision_root, "A/D", |
| id_root, D_str->data, pool)); |
| if (is) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "fetching dir by path and by node got different results"); |
| |
| |
| SVN_ERR (svn_fs_dir_entries (&entries, id_root, D_str->data, pool)); |
| val = apr_hash_get (entries, "gamma", APR_HASH_KEY_STRING); |
| if (! val) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir fetched by id doesn't have expected entry \"gamma\""); |
| |
| val = apr_hash_get (entries, "G", APR_HASH_KEY_STRING); |
| if (! val) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir fetched by id doesn't have expected entry \"G\""); |
| |
| val = apr_hash_get (entries, "H", APR_HASH_KEY_STRING); |
| if (! val) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir fetched by id doesn't have expected entry \"H\""); |
| |
| if (apr_hash_count (entries) != 3) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "dir fetched by id has unexpected number of entries"); |
| |
| |
| /* Check omega. */ |
| SVN_ERR (svn_fs_file_length (&len, id_root, omega_str->data, pool)); |
| if (len != strlen ("This is the file 'omega'.\n")) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "file fetched by id has wrong length"); |
| |
| { |
| svn_stream_t *contents_stream; |
| svn_stringbuf_t *contents_string; |
| |
| SVN_ERR (svn_fs_file_contents (&contents_stream, id_root, |
| omega_str->data, pool)); |
| SVN_ERR (svn_test__stream_to_string (&contents_string, |
| contents_stream, pool)); |
| |
| if (strcmp (contents_string->data, "This is the file 'omega'.\n") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "file fetched by id had wrong contents"); |
| } |
| |
| |
| /* Try fetching a non-ID. */ |
| err = svn_fs_file_length (&len, id_root, not_an_id_str->data, pool); |
| if (! err) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "fetching an invalid id should fail, but did not"); |
| } |
| else if (err->apr_err != SVN_ERR_FS_NOT_ID) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "fetching an invalid id failed with the wrong error"); |
| } |
| |
| |
| /* Try changing a node fetched by ID. */ |
| err = svn_fs_delete (id_root, C_str->data, pool); |
| if (! err) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting an ID path should fail, but did not"); |
| } |
| |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Helper function. Return an specific error. */ |
| static svn_error_t * |
| unexpected_node_id (svn_fs_root_t *root, |
| const char *path, |
| svn_fs_id_t *id, |
| apr_pool_t *pool) |
| { |
| svn_stringbuf_t *id_str = svn_fs_unparse_id (id, pool); |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "Path '%s' in revision '%lu' has unexpected node id '%s'.\n", |
| path, svn_fs_revision_root_revision (root), id_str->data); |
| } |
| |
| |
| static svn_error_t * |
| merge_re_id (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn, *txn2; |
| svn_fs_root_t *txn_root, *txn2_root, *rev_root; |
| svn_revnum_t greek, youngest; |
| svn_fs_id_t *root_1_id, *A_1_id, *D_1_id; |
| svn_fs_id_t *root_2_id, *A_2_id, *D_2_id; |
| svn_fs_id_t *root_3_id, *A_3_id, *D_3_id; |
| svn_fs_id_t *root_4_id, *A_4_id, *D_4_id; |
| |
| *msg = "re-id nodes after merging"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-merge-re-id", pool)); |
| |
| /* Check in a greek tree to start off with. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (test_commit_txn (&greek, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Let's track the ids of the root node, of A, and of A/D. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, greek, pool)); |
| SVN_ERR (svn_fs_node_id (&root_1_id, rev_root, "", pool)); |
| SVN_ERR (svn_fs_node_id (&A_1_id, rev_root, "A", pool)); |
| SVN_ERR (svn_fs_node_id (&D_1_id, rev_root, "A/D", pool)); |
| |
| /* Now check in some mods -- additions of files to /A/D. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, greek, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "A/D/zeta", "This is the file 'zeta'.\n" }, |
| { 'a', "A/D/zima", "This is the file 'zima'.\n" }, |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 2, pool)); |
| } |
| SVN_ERR (test_commit_txn (&youngest, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Let's track the second revision's ids for our three favorite nodes. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest, pool)); |
| SVN_ERR (svn_fs_node_id (&root_2_id, rev_root, "", pool)); |
| SVN_ERR (svn_fs_node_id (&A_2_id, rev_root, "A", pool)); |
| SVN_ERR (svn_fs_node_id (&D_2_id, rev_root, "A/D", pool)); |
| |
| /* We fully expect revision 2's ids to not have branched, meaning |
| that if the revision 1 ids are of the form `A.B', revision 2's will |
| look like `A.B+1'. */ |
| if ( !((svn_fs__id_length (root_1_id) == 2) |
| && (root_2_id[0] == root_1_id[0]) |
| && (root_2_id[1] == root_1_id[1] + 1) |
| && (root_2_id[2] == root_1_id[2]))) |
| return unexpected_node_id (rev_root, "", root_2_id, pool); |
| |
| if ( !((svn_fs__id_length (A_1_id) == 2) |
| && (A_2_id[0] == A_1_id[0]) |
| && (A_2_id[1] == A_1_id[1] + 1) |
| && (A_2_id[2] == A_1_id[2]))) |
| return unexpected_node_id (rev_root, "", A_2_id, pool); |
| |
| if ( !((svn_fs__id_length (D_1_id) == 2) |
| && (D_2_id[0] == D_1_id[0]) |
| && (D_2_id[1] == D_1_id[1] + 1) |
| && (D_2_id[2] == D_1_id[2]))) |
| return unexpected_node_id (rev_root, "", D_2_id, pool); |
| |
| /* Now, if we try to commit a transaction based on the greek tree |
| alone, the filesystem will attempt to merge all the changes |
| that have happened since the base revision of our txn into the |
| txn itself. In the following commit, we are making changes to |
| A/D that do not conflict with the changes made above. Now, when |
| this is all said and done, we need to make sure that the node IDs |
| for A, D, and / reveal an accurate ancestry. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, greek, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "A/D/data", "This is the file 'data'.\n" }, |
| { 'a', "A/D/diva", "This is the file 'diva'.\n" }, |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 2, pool)); |
| } |
| SVN_ERR (test_commit_txn (&youngest, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Let's track the second revision's ids for our three favorite nodes. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest, pool)); |
| SVN_ERR (svn_fs_node_id (&root_3_id, rev_root, "", pool)); |
| SVN_ERR (svn_fs_node_id (&A_3_id, rev_root, "A", pool)); |
| SVN_ERR (svn_fs_node_id (&D_3_id, rev_root, "A/D", pool)); |
| |
| /* Again, we expect revision 3's ids to not have branched. */ |
| if ( !((svn_fs__id_length (root_3_id) == 2) |
| && (root_3_id[0] == root_2_id[0]) |
| && (root_3_id[1] == root_2_id[1] + 1) |
| && (root_3_id[2] == root_2_id[2]))) |
| return unexpected_node_id (rev_root, "", root_3_id, pool); |
| |
| if ( !((svn_fs__id_length (A_3_id) == 2) |
| && (A_3_id[0] == A_2_id[0]) |
| && (A_3_id[1] == A_2_id[1] + 1) |
| && (A_3_id[2] == A_2_id[2]))) |
| return unexpected_node_id (rev_root, "", A_3_id, pool); |
| |
| if ( !((svn_fs__id_length (D_3_id) == 2) |
| && (D_3_id[0] == D_2_id[0]) |
| && (D_3_id[1] == D_2_id[1] + 1) |
| && (D_3_id[2] == D_2_id[2]))) |
| return unexpected_node_id (rev_root, "", D_3_id, pool); |
| |
| /* Now, for a case where we *do* expect node id branching to occur. |
| This time, we will begin a transaction that changes entries in |
| A/D, but we'll leave it hanging while we try to commit a second |
| transaction with other non-conflicting edits to A/D! */ |
| SVN_ERR (svn_fs_begin_txn (&txn2, fs, youngest, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn2_root, txn2, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "A/D/pita", "This is the file 'pita'.\n" }, |
| { 'a', "A/D/pizza", "This is the file 'pizza'.\n" }, |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn2_root, script_entries, 2, pool)); |
| } |
| |
| /* Without aborting or committing the previous txn, we will commit |
| more changes to A/D based on the original Greek Tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, greek, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "A/D/quota", "This is the file 'quota'.\n" }, |
| { 'a', "A/D/quiva", "This is the file 'quiva'.\n" }, |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 2, pool)); |
| } |
| SVN_ERR (test_commit_txn (&youngest, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Let's track the second revision's ids for our three favorite nodes. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest, pool)); |
| SVN_ERR (svn_fs_node_id (&root_4_id, rev_root, "", pool)); |
| SVN_ERR (svn_fs_node_id (&A_4_id, rev_root, "A", pool)); |
| SVN_ERR (svn_fs_node_id (&D_4_id, rev_root, "A/D", pool)); |
| |
| /* Now, we expect revision 4's ids to have branched *from revision |
| 1*. The hanging transaction above made the first branch of each |
| of these nodes, appending `.1.1' to the node ids. So we expect |
| these node id's to make a second branch, having `.2.1' after the |
| original node ids. */ |
| if ( !((svn_fs__id_length (root_4_id) == 4) |
| && (root_4_id[0] == root_1_id[0]) |
| && (root_4_id[1] == root_1_id[1]) |
| && (root_4_id[2] == 2) |
| && (root_4_id[3] == 1))) |
| return unexpected_node_id (rev_root, "", root_4_id, pool); |
| |
| if ( !((svn_fs__id_length (A_4_id) == 4) |
| && (A_4_id[0] == A_1_id[0]) |
| && (A_4_id[1] == A_1_id[1]) |
| && (A_4_id[2] == 2) |
| && (A_4_id[3] == 1))) |
| return unexpected_node_id (rev_root, "", A_4_id, pool); |
| |
| if ( !((svn_fs__id_length (D_4_id) == 4) |
| && (D_4_id[0] == D_1_id[0]) |
| && (D_4_id[1] == D_1_id[1]) |
| && (D_4_id[2] == 2) |
| && (D_4_id[3] == 1))) |
| return unexpected_node_id (rev_root, "", D_3_id, pool); |
| |
| /* Abort the hanging transaction. */ |
| SVN_ERR (svn_fs_abort_txn (txn2)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Commit with merging (committing against non-youngest). */ |
| static svn_error_t * |
| merging_commit (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *revision_root; |
| svn_revnum_t after_rev; |
| svn_revnum_t revisions[24]; |
| int i; |
| int revision_count; |
| |
| *msg = "merging commit"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Initialize our revision number stuffs. */ |
| for (i = 0; |
| i < ((sizeof (revisions)) / (sizeof (svn_revnum_t))); |
| i++) |
| revisions[i] = SVN_INVALID_REVNUM; |
| revision_count = 0; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-merging-commit", pool)); |
| revisions[revision_count++] = 0; /* the brand spankin' new revision */ |
| |
| /***********************************************************************/ |
| /* REVISION 0 */ |
| /***********************************************************************/ |
| |
| /* In one txn, create and commit the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| |
| /***********************************************************************/ |
| /* REVISION 1 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 20, pool)); |
| } |
| SVN_ERR (svn_fs_close_txn (txn)); |
| revisions[revision_count++] = after_rev; |
| |
| /* Let's add a directory and some files to the tree, and delete |
| 'iota' */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[revision_count-1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_dir (txn_root, "A/D/I", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/I/delta", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/I/delta", "This is the file 'delta'.\n", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/I/epsilon", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/I/epsilon", "This is the file 'epsilon'.\n", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/C/kappa", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/C/kappa", "This is the file 'kappa'.\n", pool)); |
| SVN_ERR (svn_fs_delete (txn_root, "iota", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| |
| /***********************************************************************/ |
| /* REVISION 2 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 23, pool)); |
| } |
| SVN_ERR (svn_fs_close_txn (txn)); |
| revisions[revision_count++] = after_rev; |
| |
| /* We don't think the A/D/H directory is pulling it's weight...let's |
| knock it off. Oh, and let's re-add iota, too. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[revision_count-1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/H", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "iota", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", "This is the new file 'iota'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| |
| /***********************************************************************/ |
| /* REVISION 3 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the new file 'iota'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 20, pool)); |
| } |
| SVN_ERR (svn_fs_close_txn (txn)); |
| revisions[revision_count++] = after_rev; |
| |
| /* Delete iota (yet again). */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[revision_count-1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete (txn_root, "iota", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| |
| /***********************************************************************/ |
| /* REVISION 4 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 19, pool)); |
| } |
| SVN_ERR (svn_fs_close_txn (txn)); |
| revisions[revision_count++] = after_rev; |
| |
| /***********************************************************************/ |
| /* GIVEN: A and B, with common ancestor ANCESTOR, where A and B |
| directories, and E, an entry in either A, B, or ANCESTOR. |
| |
| For every E, the following cases exist: |
| - E exists in neither ANCESTOR nor A. |
| - E doesn't exist in ANCESTOR, and has been added to A. |
| - E exists in ANCESTOR, but has been deleted from A. |
| - E exists in both ANCESTOR and A ... |
| - but refers to different node revisions. |
| - and refers to the same node revision. |
| |
| The same set of possible relationships with ANCESTOR holds for B, |
| so there are thirty-six combinations. The matrix is symmetrical |
| with A and B reversed, so we only have to describe one triangular |
| half, including the diagonal --- 21 combinations. |
| |
| Our goal here is to test all the possible scenarios that can |
| occur given the above boolean logic table, and to make sure that |
| the results we get are as expected. |
| |
| The test cases below have the following features: |
| |
| - They run straight through the scenarios as described in the |
| `structure' document at this time. |
| |
| - In each case, a txn is begun based on some revision (ANCESTOR), |
| is modified into a new tree (B), and then is attempted to be |
| committed (which happens against the head of the tree, A). |
| |
| - If the commit is successful (and is *expected* to be such), |
| that new revision (which exists now as a result of the |
| successful commit) is thoroughly tested for accuracy of tree |
| entries, and in the case of files, for their contents. It is |
| important to realize that these successful commits are |
| advancing the head of the tree, and each one effective becomes |
| the new `A' described in further test cases. |
| */ |
| /***********************************************************************/ |
| |
| /* (6) E exists in neither ANCESTOR nor A. */ |
| { |
| /* (1) E exists in neither ANCESTOR nor B. Can't occur, by |
| assumption that E exists in either A, B, or ancestor. */ |
| |
| /* (1) E has been added to B. Add E in the merged result. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[0], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "theta", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "theta", "This is the file 'theta'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| |
| /*********************************************************************/ |
| /* REVISION 5 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, |
| expected_entries, |
| 20, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| |
| /* (1) E has been deleted from B. Can't occur, by assumption that |
| E doesn't exist in ANCESTOR. */ |
| |
| /* (3) E exists in both ANCESTOR and B. Can't occur, by |
| assumption that E doesn't exist in ancestor. */ |
| } |
| |
| /* (5) E doesn't exist in ANCESTOR, and has been added to A. */ |
| { |
| /* (1) E doesn't exist in ANCESTOR, and has been added to B. |
| Conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[4], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "theta", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "theta", "This is another file 'theta'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/theta", pool)); |
| |
| /* (1) E exists in ANCESTOR, but has been deleted from B. Can't |
| occur, by assumption that E doesn't exist in ANCESTOR. */ |
| |
| /* (3) E exists in both ANCESTOR and B. Can't occur, by assumption |
| that E doesn't exist in ANCESTOR. */ |
| } |
| |
| /* (4) E exists in ANCESTOR, but has been deleted from A */ |
| { |
| /* (1) E exists in ANCESTOR, but has been deleted from B. If |
| neither delete was a result of a rename, then omit E from the |
| merged tree. Otherwise, conflict. */ |
| /* ### cmpilato todo: the rename case isn't actually handled by |
| merge yet, so we know we won't get a conflict here. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/H", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| /*********************************************************************/ |
| /* REVISION 6 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, |
| expected_entries, |
| 20, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| |
| /* Try deleting a file F inside a subtree S where S does not exist |
| in the most recent revision, but does exist in the ancestor |
| tree. This should conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete (txn_root, "A/D/H/omega", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/A/D/H", pool)); |
| |
| /* E exists in both ANCESTOR and B ... */ |
| { |
| /* (1) but refers to different nodes. Conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/H", pool)); |
| SVN_ERR (svn_fs_make_dir (txn_root, "A/D/H", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| revisions[revision_count++] = after_rev; |
| |
| /*********************************************************************/ |
| /* REVISION 7 */ |
| /*********************************************************************/ |
| |
| /* Re-remove A/D/H because future tests expect it to be absent. */ |
| { |
| SVN_ERR (svn_fs_begin_txn |
| (&txn, fs, revisions[revision_count - 1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/H", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| revisions[revision_count++] = after_rev; |
| } |
| |
| /*********************************************************************/ |
| /* REVISION 8 (looks exactly like revision 6, we hope) */ |
| /*********************************************************************/ |
| |
| /* (1) but refers to different revisions of the same node. |
| Conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/H/zeta", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/A/D/H", pool)); |
| |
| /* (1) and refers to the same node revision. Omit E from the |
| merged tree. This is already tested in Merge-Test 3 |
| (A/D/H/chi, A/D/H/psi, e.g.), but we'll test it here again |
| anyway. A little paranoia never hurt anyone. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete (txn_root, "A/mu", pool)); /* unrelated change */ |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| |
| /*********************************************************************/ |
| /* REVISION 9 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, |
| expected_entries, |
| 19, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| } |
| } |
| |
| /* Preparation for upcoming tests. |
| We make a new head revision, with A/mu restored, but containing |
| slightly different contents than its first incarnation. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[revision_count-1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/mu", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/mu", "A new file 'mu'.\n", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/xi", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/G/xi", "This is the file 'xi'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| /*********************************************************************/ |
| /* REVISION 10 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "A new file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/G/xi", "This is the file 'xi'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 21, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| |
| /* (3) E exists in both ANCESTOR and A, but refers to different |
| nodes. */ |
| { |
| /* (1) E exists in both ANCESTOR and B, but refers to different |
| nodes, and not all nodes are directories. Conflict. */ |
| |
| /* ### kff todo: A/mu's contents will be exactly the same. |
| If the fs ever starts optimizing this case, these tests may |
| start to fail. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_delete (txn_root, "A/mu", pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/mu", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/mu", "This is the file 'mu'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/A/mu", pool)); |
| |
| /* (1) E exists in both ANCESTOR and B, but refers to different |
| revisions of the same node. Conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/mu", "A change to file 'mu'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/A/mu", pool)); |
| |
| /* (1) E exists in both ANCESTOR and B, and refers to the same |
| node revision. Replace E with A's node revision. */ |
| { |
| svn_stringbuf_t *old_mu_contents; |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__get_file_contents |
| (txn_root, "A/mu", &old_mu_contents, pool)); |
| if ((! old_mu_contents) || (strcmp (old_mu_contents->data, |
| "This is the file 'mu'.\n") != 0)) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "got wrong contents from an old revision tree"); |
| } |
| SVN_ERR (svn_fs_make_file (txn_root, "A/sigma", pool)); |
| SVN_ERR (svn_test__set_file_contents /* unrelated change */ |
| (txn_root, "A/sigma", "This is the file 'sigma'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| /*********************************************************************/ |
| /* REVISION 11 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "A new file 'mu'.\n" }, |
| { "A/sigma", "This is the file 'sigma'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/G/xi", "This is the file 'xi'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, |
| expected_entries, |
| 22, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| } |
| } |
| |
| /* Preparation for upcoming tests. |
| We make a new head revision. There are two changes in the new |
| revision: A/B/lambda has been modified. We will also use the |
| recent addition of A/D/G/xi, treated as a modification to |
| A/D/G. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[revision_count-1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/B/lambda", "Change to file 'lambda'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| /*********************************************************************/ |
| /* REVISION 12 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "A new file 'mu'.\n" }, |
| { "A/sigma", "This is the file 'sigma'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "Change to file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/G/xi", "This is the file 'xi'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, expected_entries, |
| 22, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| |
| /* (2) E exists in both ANCESTOR and A, but refers to different |
| revisions of the same node. */ |
| { |
| /* (1a) E exists in both ANCESTOR and B, but refers to different |
| revisions of the same file node. Conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/B/lambda", "A different change to 'lambda'.\n", |
| pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/A/B/lambda", pool)); |
| |
| /* (1b) E exists in both ANCESTOR and B, but refers to different |
| revisions of the same directory node. Merge A/E and B/E, |
| recursively. Succeed, because no conflict beneath E. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/nu", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/G/nu", "This is the file 'nu'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| /*********************************************************************/ |
| /* REVISION 13 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "A new file 'mu'.\n" }, |
| { "A/sigma", "This is the file 'sigma'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "Change to file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/G/xi", "This is the file 'xi'.\n" }, |
| { "A/D/G/nu", "This is the file 'nu'.\n" }, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, |
| expected_entries, |
| 23, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| |
| /* (1c) E exists in both ANCESTOR and B, but refers to different |
| revisions of the same directory node. Merge A/E and B/E, |
| recursively. Fail, because conflict beneath E. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/xi", pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/G/xi", "This is a different file 'xi'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/A/D/G/xi", pool)); |
| |
| /* (1) E exists in both ANCESTOR and B, and refers to the same node |
| revision. Replace E with A's node revision. */ |
| { |
| svn_stringbuf_t *old_lambda_ctnts; |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__get_file_contents |
| (txn_root, "A/B/lambda", &old_lambda_ctnts, pool)); |
| if ((! old_lambda_ctnts) |
| || (strcmp (old_lambda_ctnts->data, |
| "This is the file 'lambda'.\n") != 0)) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "got wrong contents from an old revision tree"); |
| } |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/G/rho", |
| "This is an irrelevant change to 'rho'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| /*********************************************************************/ |
| /* REVISION 14 */ |
| /*********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "theta", "This is the file 'theta'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "A new file 'mu'.\n" }, |
| { "A/sigma", "This is the file 'sigma'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "Change to file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/C/kappa", "This is the file 'kappa'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is an irrelevant change to 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/G/xi", "This is the file 'xi'.\n" }, |
| { "A/D/G/nu", "This is the file 'nu'.\n"}, |
| { "A/D/I", 0 }, |
| { "A/D/I/delta", "This is the file 'delta'.\n" }, |
| { "A/D/I/epsilon", "This is the file 'epsilon'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (revision_root, |
| expected_entries, |
| 23, pool)); |
| } |
| revisions[revision_count++] = after_rev; |
| } |
| } |
| |
| /* (1) E exists in both ANCESTOR and A, and refers to the same node |
| revision. */ |
| { |
| /* (1) E exists in both ANCESTOR and B, and refers to the same |
| node revision. Nothing has happened to ANCESTOR/E, so no |
| change is necessary. */ |
| |
| /* This has now been tested about fifty-four trillion times. We |
| don't need to test it again here. */ |
| } |
| |
| /* E exists in ANCESTOR, but has been deleted from A. E exists in |
| both ANCESTOR and B but refers to different revisions of the same |
| node. Conflict. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, revisions[1], pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", "New contents for 'iota'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, "/iota", pool)); |
| |
| /* Close the filesystem. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| copy_test (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t after_rev; |
| |
| *msg = "copying and tracking copy history"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-copy-test", pool)); |
| |
| /* In first txn, create and commit the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* In second txn, copy the file A/D/G/pi into the subtree A/D/H as |
| pi2. Change that file's contents to state its new name. Along |
| the way, test that the copy history was preserved both during the |
| transaction and after the commit. */ |
| |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_copy (rev_root, "A/D/G/pi", |
| txn_root, "A/D/H/pi2", |
| pool)); |
| { /* Check that copy history was preserved. */ |
| svn_revnum_t rev; |
| const char *path; |
| |
| SVN_ERR (svn_fs_copied_from (&rev, &path, txn_root, "A/D/H/pi2", pool)); |
| |
| if (rev != after_rev) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "pre-commit copy history not preserved (rev lost) for A/D/H/pi2"); |
| |
| if (strcmp (path, "A/D/G/pi") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "pre-commit copy history not preserved (path lost) for A/D/H/pi2"); |
| } |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/H/pi2", "This is the file 'pi2'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| { /* Check that copy history is still preserved _after_ the commit. */ |
| svn_fs_root_t *root; |
| svn_revnum_t rev; |
| const char *path; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/D/H/pi2", pool)); |
| |
| if (rev != (after_rev - 1)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "post-commit copy history wrong (rev) for A/D/H/pi2"); |
| |
| if (strcmp (path, "A/D/G/pi") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "post-commit copy history wrong (path) for A/D/H/pi2"); |
| } |
| |
| /* Let's copy the copy we just made, to make sure copy history gets |
| chained correctly. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_copy (rev_root, "A/D/H/pi2", txn_root, "A/D/H/pi3", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| { /* Check the copy history. */ |
| svn_fs_root_t *root; |
| svn_revnum_t rev; |
| const char *path; |
| |
| /* Check that the original copy still has its old history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, (after_rev - 1), pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/D/H/pi2", pool)); |
| |
| if (rev != (after_rev - 2)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "first copy history wrong (rev) for A/D/H/pi2"); |
| |
| if (strcmp (path, "A/D/G/pi") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "first copy history wrong (path) for A/D/H/pi2"); |
| |
| /* Check that the copy of the copy has the right history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/D/H/pi3", pool)); |
| |
| if (rev != (after_rev - 1)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "second copy history wrong (rev) for A/D/H/pi3"); |
| |
| if (strcmp (path, "A/D/H/pi2") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "second copy history wrong (path) for A/D/H/pi3"); |
| } |
| |
| /* Commit a regular change to a copy, make sure the copy history |
| isn't inherited. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/H/pi3", "This is the file 'pi3'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| { /* Check the copy history. */ |
| svn_fs_root_t *root; |
| svn_revnum_t rev; |
| const char *path; |
| |
| /* Check that the copy still has its history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, (after_rev - 1), pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/D/H/pi3", pool)); |
| |
| if (rev != (after_rev - 2)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (rev) for A/D/H/pi3"); |
| |
| if (strcmp (path, "A/D/H/pi2") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (path) for A/D/H/pi3"); |
| |
| /* Check that the next revision after the copy has no copy history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/D/H/pi3", pool)); |
| |
| if (rev != SVN_INVALID_REVNUM) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (rev) for A/D/H/pi3"); |
| |
| if (path != NULL) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (path) for A/D/H/pi3"); |
| } |
| |
| /* Then, as if that wasn't fun enough, copy the whole subtree A/D/H |
| into the root directory as H2! */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_copy (rev_root, "A/D/H", txn_root, "H2", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| { /* Check the copy history. */ |
| svn_fs_root_t *root; |
| svn_revnum_t rev; |
| const char *path; |
| |
| /* Check that the top of the copy has history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "H2", pool)); |
| |
| if (rev != (after_rev - 1)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (rev) for H2"); |
| |
| if (strcmp (path, "A/D/H") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (path) for H2"); |
| |
| /* Check that a random file under H2 reports no copy history. */ |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "H2/omega", pool)); |
| |
| if (rev != SVN_INVALID_REVNUM) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (rev) for H2/omega"); |
| |
| if (path != NULL) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (path) for H2/omega"); |
| |
| /* Note that H2/pi2 still has copy history, though. See the doc |
| string for svn_fs_copied_from() for more on this. */ |
| } |
| |
| /* Let's live dangerously. What happens if we copy a path into one |
| of its own children. Looping filesystem? Cyclic ancestry? |
| Another West Virginia family tree with no branches? We certainly |
| hope that's not the case. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_copy (rev_root, "A/B", txn_root, "A/B/E/B", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| { /* Check the copy history. */ |
| svn_fs_root_t *root; |
| svn_revnum_t rev; |
| const char *path; |
| |
| /* Check that the copy has history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/B/E/B", pool)); |
| |
| if (rev != (after_rev - 1)) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (rev) for A/B/E/B"); |
| |
| if (strcmp (path, "A/B") != 0) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (path) for A/B/E/B"); |
| |
| /* Check that the original does not have copy history. */ |
| SVN_ERR (svn_fs_revision_root (&root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, root, "A/B", pool)); |
| |
| if (rev != SVN_INVALID_REVNUM) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (rev) for A/B"); |
| |
| if (path != NULL) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "copy history wrong (path) for A/B"); |
| } |
| |
| /* After all these changes, let's see if the filesystem looks as we |
| would expect it to. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" }, |
| { "H2", 0 }, |
| { "H2/chi", "This is the file 'chi'.\n" }, |
| { "H2/pi2", "This is the file 'pi2'.\n" }, |
| { "H2/pi3", "This is the file 'pi3'.\n" }, |
| { "H2/psi", "This is the file 'psi'.\n" }, |
| { "H2/omega", "This is the file 'omega'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/E/B", 0 }, |
| { "A/B/E/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E/B/E", 0 }, |
| { "A/B/E/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/E/B/F", 0 }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/pi2", "This is the file 'pi2'.\n" }, |
| { "A/D/H/pi3", "This is the file 'pi3'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_test__validate_tree (rev_root, expected_entries, |
| 34, pool)); |
| } |
| /* Close the filesystem. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| link_test (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t after_rev; |
| |
| *msg = "linking, so no copy history"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-link-test", pool)); |
| |
| /* In first txn, create and commit the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* In second txn, link the file A/D/G/pi into the subtree A/D/G as |
| pi2. Change that file's contents to state its new name. Along |
| the way, test that no copy history was preserved, and the ids are |
| the same. */ |
| |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_link (rev_root, "A/D/G/pi", |
| txn_root, "A/D/G/pi2", |
| pool)); |
| |
| /* Check that no copy history was generated. */ |
| { |
| svn_revnum_t rev; |
| const char *path; |
| |
| SVN_ERR (svn_fs_copied_from (&rev, &path, txn_root, "A/D/G/pi2", pool)); |
| |
| if (SVN_IS_VALID_REVNUM (rev)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: copy rev present when should be absent on `%s'", |
| "A/D/G/pi2"); |
| |
| if (path) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: copy path present when should be absent on `%s'", |
| "A/D/G/pi2"); |
| } |
| |
| /* Test that the node id is the same on the two files in the txn. */ |
| { |
| svn_fs_id_t *orig_id, *link_id; |
| |
| SVN_ERR (svn_fs_node_id (&orig_id, txn_root, "A/D/G/pi", pool)); |
| SVN_ERR (svn_fs_node_id (&link_id, txn_root, "A/D/G/pi2", pool)); |
| |
| if (! svn_fs__id_eq (orig_id, link_id)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: orig id not same as link id (`%s', `%s')", |
| "A/D/G/pi", "A/D/G/pi2"); |
| } |
| |
| /* Commit the file. */ |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/G/pi2", "This is the file 'pi2'.\n", pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Get a revision root on the head. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| |
| /* Check that there's _still_ no copy history. */ |
| { |
| svn_revnum_t rev; |
| const char *path; |
| |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_copied_from (&rev, &path, rev_root, "A/D/G/pi2", pool)); |
| |
| if (SVN_IS_VALID_REVNUM (rev)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: copy rev wrongly present on committed `%s'", |
| "A/D/G/pi2"); |
| |
| if (path) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: copy path wrongly present on committed `%s'", |
| "A/D/G/pi2"); |
| } |
| |
| /* Test that the node id has changed now, since we changed the file. */ |
| { |
| svn_fs_id_t *orig_id, *link_id; |
| |
| SVN_ERR (svn_fs_node_id (&orig_id, rev_root, "A/D/G/pi", pool)); |
| SVN_ERR (svn_fs_node_id (&link_id, rev_root, "A/D/G/pi2", pool)); |
| |
| if (svn_fs__id_eq (orig_id, link_id)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: orig id same as newly committed link id (`%s', `%s')", |
| "A/D/G/pi", "A/D/G/pi2"); |
| } |
| |
| /* Link the file A/D/G/pi2 to A/D/G/pi3 and commit, *without* |
| changing pi3. */ |
| |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, after_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_fs_link (rev_root, "A/D/G/pi2", |
| txn_root, "A/D/G/pi3", |
| pool)); |
| SVN_ERR (test_commit_txn (&after_rev, txn, NULL, pool)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Test that the node id has changed now, since we changed the file. */ |
| { |
| /* The node id's will be the same. BAD. See below: |
| |
| ### todo: this is, of course, scary, because there's a hard link |
| in the filesystem. svn_fs_link() is dangerous, and we will have |
| to modify it to protect against hard links. See issue #419. |
| if this clause in the test starts failing, it probably means |
| the issue has been fixed, so the test needs to be changed, not |
| Subversion. |
| */ |
| svn_fs_id_t *orig_id, *link_id; |
| |
| SVN_ERR (svn_fs_node_id (&orig_id, rev_root, "A/D/G/pi", pool)); |
| SVN_ERR (svn_fs_node_id (&link_id, rev_root, "A/D/G/pi2", pool)); |
| |
| if (svn_fs__id_eq (orig_id, link_id)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "link_test: orig not same as unchanged committed link (`%s', `%s')", |
| "A/D/G/pi", "A/D/G/pi2"); |
| } |
| |
| /* Close the filesystem. */ |
| SVN_ERR (svn_fs_close_fs (fs)); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This tests deleting of mutable nodes. We build a tree in a |
| * transaction, then try to delete various items in the tree. We |
| * never commit the tree, so every entry being deleted points to a |
| * mutable node. |
| * |
| * ### todo: this test was written before commits worked. It might |
| * now be worthwhile to combine it with delete(). |
| */ |
| static svn_error_t * |
| delete_mutables (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_error_t *err; |
| |
| *msg = "delete mutable nodes from directories"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-del-from-dir", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* Baby, it's time to test like you've never tested before. We do |
| * the following, in this order: |
| * |
| * 1. Delete a single file somewhere, succeed. |
| * 2. Delete two files of three, then make sure the third remains. |
| * 3. Try to delete that directory, get the right error. |
| * 4. Delete the third and last file. |
| * 5. Try again to delete the dir, succeed. |
| * 6. Delete one of the natively empty dirs, succeed. |
| * 7. Try to delete root, fail. |
| * 8. Try to delete a dir whose only entries are also dirs, fail. |
| * 9. Try to delete a top-level file, succeed. |
| * |
| * Specifically, that's: |
| * |
| * 1. Delete A/D/gamma. |
| * 2. Delete A/D/G/pi, A/D/G/rho. |
| * 3. Try to delete A/D/G, fail. |
| * 4. Delete A/D/G/tau. |
| * 5. Try again to delete A/D/G, succeed. |
| * 6. Delete A/C. |
| * 7. Try to delete /, fail. |
| * 8. Try to delete A/D, fail. |
| * 9. Try to delete iota, succeed. |
| * |
| * Before and after each deletion or attempted deletion, we probe |
| * the affected directory, to make sure everything is as it should |
| * be. |
| */ |
| |
| /* 1 */ |
| { |
| svn_fs_id_t *gamma_id; |
| SVN_ERR (svn_fs_node_id (&gamma_id, txn_root, "A/D/gamma", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (check_id_present (fs, gamma_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "A/D/gamma", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (check_id_absent (fs, gamma_id, pool)); |
| } |
| |
| /* 2 */ |
| { |
| svn_fs_id_t *pi_id, *rho_id, *tau_id; |
| SVN_ERR (svn_fs_node_id (&pi_id, txn_root, "A/D/G/pi", pool)); |
| SVN_ERR (svn_fs_node_id (&rho_id, txn_root, "A/D/G/rho", pool)); |
| SVN_ERR (svn_fs_node_id (&tau_id, txn_root, "A/D/G/tau", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "pi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "rho", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| SVN_ERR (check_id_present (fs, pi_id, pool)); |
| SVN_ERR (check_id_present (fs, rho_id, pool)); |
| SVN_ERR (check_id_present (fs, tau_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "A/D/G/pi", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "A/D/G", "pi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "rho", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| SVN_ERR (check_id_absent (fs, pi_id, pool)); |
| SVN_ERR (check_id_present (fs, rho_id, pool)); |
| SVN_ERR (check_id_present (fs, tau_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "A/D/G/rho", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "A/D/G", "pi", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "A/D/G", "rho", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| SVN_ERR (check_id_absent (fs, pi_id, pool)); |
| SVN_ERR (check_id_absent (fs, rho_id, pool)); |
| SVN_ERR (check_id_present (fs, tau_id, pool)); |
| } |
| |
| /* 3 */ |
| { |
| svn_fs_id_t *G_id; |
| SVN_ERR (svn_fs_node_id (&G_id, txn_root, "A/D/G", pool)); |
| |
| SVN_ERR (check_id_present (fs, G_id, pool)); |
| err = svn_fs_delete (txn_root, "A/D/G", pool); /* fail */ |
| |
| if (err && (err->apr_err != SVN_ERR_FS_DIR_NOT_EMPTY)) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting non-empty directory got wrong error"); |
| } |
| else if (! err) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting non-empty directory failed to get error"); |
| } |
| |
| SVN_ERR (check_entry_present (txn_root, "A/D", "G", pool)); |
| SVN_ERR (check_id_present (fs, G_id, pool)); |
| } |
| |
| /* 4 */ |
| { |
| svn_fs_id_t *tau_id; |
| SVN_ERR (svn_fs_node_id (&tau_id, txn_root, "A/D/G/tau", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| SVN_ERR (check_id_present (fs, tau_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "A/D/G/tau", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "A/D/G", "tau", pool)); |
| SVN_ERR (check_id_absent (fs, tau_id, pool)); |
| } |
| |
| /* 5 */ |
| { |
| svn_fs_id_t *G_id; |
| SVN_ERR (svn_fs_node_id (&G_id, txn_root, "A/D/G", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "A/D", "G", pool)); |
| SVN_ERR (check_id_present (fs, G_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "A/D/G", pool)); /* succeed */ |
| |
| SVN_ERR (check_entry_absent (txn_root, "A/D", "G", pool)); |
| SVN_ERR (check_id_absent (fs, G_id, pool)); |
| } |
| |
| /* 6 */ |
| { |
| svn_fs_id_t *C_id; |
| SVN_ERR (svn_fs_node_id (&C_id, txn_root, "A/C", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "A", "C", pool)); |
| SVN_ERR (check_id_present (fs, C_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "A/C", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "A", "C", pool)); |
| SVN_ERR (check_id_absent (fs, C_id, pool)); |
| } |
| |
| /* 7 */ |
| { |
| svn_fs_id_t *root_id; |
| SVN_ERR (svn_fs_node_id (&root_id, txn_root, "", pool)); |
| |
| err = svn_fs_delete (txn_root, "", pool); |
| |
| if (err && (err->apr_err != SVN_ERR_FS_ROOT_DIR)) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting root directory got wrong error"); |
| } |
| else if (! err) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting root directory failed to get error"); |
| } |
| |
| SVN_ERR (check_id_present (fs, root_id, pool)); |
| } |
| |
| /* 8 */ |
| { |
| svn_fs_id_t *D_id; |
| SVN_ERR (svn_fs_node_id (&D_id, txn_root, "A/D", pool)); |
| |
| err = svn_fs_delete (txn_root, "A/D", pool); |
| |
| if (err && (err->apr_err != SVN_ERR_FS_DIR_NOT_EMPTY)) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting non-empty directory got wrong error"); |
| } |
| else if (! err) |
| { |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "deleting non-empty directory failed to get error"); |
| } |
| |
| SVN_ERR (check_entry_present (txn_root, "A", "D", pool)); |
| SVN_ERR (check_id_present (fs, D_id, pool)); |
| } |
| |
| /* 9 */ |
| { |
| svn_fs_id_t *iota_id; |
| SVN_ERR (svn_fs_node_id (&iota_id, txn_root, "iota", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "", "iota", pool)); |
| SVN_ERR (check_id_present (fs, iota_id, pool)); |
| |
| SVN_ERR (svn_fs_delete (txn_root, "iota", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "", "iota", pool)); |
| SVN_ERR (check_id_absent (fs, iota_id, pool)); |
| } |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This tests deleting in general. |
| * |
| * ### todo: this test was written after (and independently of) |
| * delete_mutables(). It might be worthwhile to combine them. |
| */ |
| static svn_error_t * |
| delete (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_revnum_t new_rev; |
| svn_error_t *err; |
| |
| *msg = "delete nodes tree"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* This function tests 5 cases: |
| * |
| * 1. Delete mutable file. |
| * 2. Delete mutable directory. |
| * 3. Delete mutable directory with immutable nodes. |
| * 4. Delete immutable file. |
| * 5. Delete immutable directory. |
| */ |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-del-tree", pool)); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* 1. Delete mutable file. */ |
| { |
| svn_fs_id_t *iota_id, *gamma_id; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/C", 0 }, |
| { "A/B/F", 0 }, |
| { "A/D", 0 }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| /* Check nodes revision ID is gone. */ |
| SVN_ERR (svn_fs_node_id (&iota_id, txn_root, "iota", pool)); |
| SVN_ERR (svn_fs_node_id (&gamma_id, txn_root, "A/D/gamma", pool)); |
| |
| SVN_ERR (check_entry_present (txn_root, "", "iota", pool)); |
| SVN_ERR (check_id_present (fs, iota_id, pool)); |
| SVN_ERR (check_id_present (fs, gamma_id, pool)); |
| |
| /* Try deleting a mutable file with plain delete. */ |
| SVN_ERR (svn_fs_delete (txn_root, "iota", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "", "iota", pool)); |
| SVN_ERR (check_id_absent (fs, iota_id, pool)); |
| |
| /* Try deleting a mutable file with delete_tree. */ |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/gamma", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (check_id_absent (fs, gamma_id, pool)); |
| |
| /* Validate the tree. */ |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 18, pool)); |
| } |
| /* Abort transaction. */ |
| SVN_ERR (svn_fs_abort_txn (txn)); |
| |
| /* 2. Delete mutable directory. */ |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| { |
| svn_fs_id_t *A_id, *mu_id, *B_id, *lambda_id, *E_id, *alpha_id, |
| *beta_id, *F_id, *C_id, *D_id, *gamma_id, *H_id, *chi_id, |
| *psi_id, *omega_id, *G_id, *pi_id, *rho_id, *tau_id; |
| |
| /* Check nodes revision ID is gone. */ |
| SVN_ERR (svn_fs_node_id (&A_id, txn_root, "/A", pool)); |
| SVN_ERR (check_entry_present (txn_root, "", "A", pool)); |
| SVN_ERR (svn_fs_node_id (&mu_id, txn_root, "/A/mu", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "mu", pool)); |
| SVN_ERR (svn_fs_node_id (&B_id, txn_root, "/A/B", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "B", pool)); |
| SVN_ERR (svn_fs_node_id (&lambda_id, txn_root, "/A/B/lambda", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "lambda", pool)); |
| SVN_ERR (svn_fs_node_id (&E_id, txn_root, "/A/B/E", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "E", pool)); |
| SVN_ERR (svn_fs_node_id (&alpha_id, txn_root, "/A/B/E/alpha", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B/E", "alpha", pool)); |
| SVN_ERR (svn_fs_node_id (&beta_id, txn_root, "/A/B/E/beta", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B/E", "beta", pool)); |
| SVN_ERR (svn_fs_node_id (&F_id, txn_root, "/A/B/F", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "F", pool)); |
| SVN_ERR (svn_fs_node_id (&C_id, txn_root, "/A/C", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "C", pool)); |
| SVN_ERR (svn_fs_node_id (&D_id, txn_root, "/A/D", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "D", pool)); |
| SVN_ERR (svn_fs_node_id (&gamma_id, txn_root, "/A/D/gamma", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (svn_fs_node_id (&H_id, txn_root, "/A/D/H", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "H", pool)); |
| SVN_ERR (svn_fs_node_id (&chi_id, txn_root, "/A/D/H/chi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "chi", pool)); |
| SVN_ERR (svn_fs_node_id (&psi_id, txn_root, "/A/D/H/psi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "psi", pool)); |
| SVN_ERR (svn_fs_node_id (&omega_id, txn_root, "/A/D/H/omega", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "omega", pool)); |
| SVN_ERR (svn_fs_node_id (&G_id, txn_root, "/A/D/G", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "G", pool)); |
| SVN_ERR (svn_fs_node_id (&pi_id, txn_root, "/A/D/G/pi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "pi", pool)); |
| SVN_ERR (svn_fs_node_id (&rho_id, txn_root, "/A/D/G/rho", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "rho", pool)); |
| SVN_ERR (svn_fs_node_id (&tau_id, txn_root, "/A/D/G/tau", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| |
| /* Try deleting a mutable empty dir with plain delete. */ |
| SVN_ERR (svn_fs_delete (txn_root, "A/C", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "A", "C", pool)); |
| SVN_ERR (check_id_absent (fs, C_id, pool)); |
| |
| /* Try deleting a mutable empty dir with delete_tree. */ |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/B/F", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "A/B", "F", pool)); |
| SVN_ERR (check_id_absent (fs, F_id, pool)); |
| |
| /* Try an unsuccessful delete of a non-empty dir. */ |
| err = svn_fs_delete (txn_root, "A", pool); |
| if (err && (err->apr_err != SVN_ERR_FS_DIR_NOT_EMPTY)) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "delete failed as expected, but for wrong reason"); |
| } |
| else if (! err) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "delete succeeded when expected to fail"); |
| } |
| |
| /* Try a successful delete of a non-empty dir. */ |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "", "A", pool)); |
| SVN_ERR (check_id_absent (fs, A_id, pool)); |
| SVN_ERR (check_id_absent (fs, mu_id, pool)); |
| SVN_ERR (check_id_absent (fs, B_id, pool)); |
| SVN_ERR (check_id_absent (fs, lambda_id, pool)); |
| SVN_ERR (check_id_absent (fs, E_id, pool)); |
| SVN_ERR (check_id_absent (fs, alpha_id, pool)); |
| SVN_ERR (check_id_absent (fs, beta_id, pool)); |
| SVN_ERR (check_id_absent (fs, D_id, pool)); |
| SVN_ERR (check_id_absent (fs, gamma_id, pool)); |
| SVN_ERR (check_id_absent (fs, H_id, pool)); |
| SVN_ERR (check_id_absent (fs, chi_id, pool)); |
| SVN_ERR (check_id_absent (fs, psi_id, pool)); |
| SVN_ERR (check_id_absent (fs, omega_id, pool)); |
| SVN_ERR (check_id_absent (fs, G_id, pool)); |
| SVN_ERR (check_id_absent (fs, pi_id, pool)); |
| SVN_ERR (check_id_absent (fs, rho_id, pool)); |
| SVN_ERR (check_id_absent (fs, tau_id, pool)); |
| |
| /* Validate the tree. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" } }; |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 1, pool)); |
| } |
| } |
| |
| /* Abort transaction. */ |
| SVN_ERR (svn_fs_abort_txn (txn)); |
| |
| /* 3. Delete mutable directory with immutable nodes. */ |
| |
| /* Prepare a txn to receive the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| /* Create the greek tree. */ |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| |
| /* Commit the greek tree. */ |
| SVN_ERR (svn_fs_commit_txn (NULL, &new_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Create new transaction. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, new_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| { |
| svn_fs_id_t *A_id, *mu_id, *B_id, *lambda_id, *E_id, *alpha_id, |
| *beta_id, *F_id, *C_id, *D_id, *gamma_id, *H_id, *chi_id, |
| *psi_id, *omega_id, *G_id, *pi_id, *rho_id, *tau_id, *sigma_id; |
| |
| /* Create A/D/G/sigma. This makes all component of A/D/G |
| mutable. */ |
| SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/sigma", pool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/sigma", |
| "This is another file 'sigma'.\n", pool)); |
| |
| /* Check mutable nodes revision ID is removed and immutable ones |
| still exist. */ |
| SVN_ERR (svn_fs_node_id (&A_id, txn_root, "/A", pool)); |
| SVN_ERR (check_entry_present (txn_root, "", "A", pool)); |
| SVN_ERR (svn_fs_node_id (&mu_id, txn_root, "/A/mu", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "mu", pool)); |
| SVN_ERR (svn_fs_node_id (&B_id, txn_root, "/A/B", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "B", pool)); |
| SVN_ERR (svn_fs_node_id (&lambda_id, txn_root, "/A/B/lambda", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "lambda", pool)); |
| SVN_ERR (svn_fs_node_id (&E_id, txn_root, "/A/B/E", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "E", pool)); |
| SVN_ERR (svn_fs_node_id (&alpha_id, txn_root, "/A/B/E/alpha", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B/E", "alpha", pool)); |
| SVN_ERR (svn_fs_node_id (&beta_id, txn_root, "/A/B/E/beta", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B/E", "beta", pool)); |
| SVN_ERR (svn_fs_node_id (&F_id, txn_root, "/A/B/F", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "F", pool)); |
| SVN_ERR (svn_fs_node_id (&C_id, txn_root, "/A/C", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "C", pool)); |
| SVN_ERR (svn_fs_node_id (&D_id, txn_root, "/A/D", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "D", pool)); |
| SVN_ERR (svn_fs_node_id (&gamma_id, txn_root, "/A/D/gamma", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (svn_fs_node_id (&H_id, txn_root, "/A/D/H", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "H", pool)); |
| SVN_ERR (svn_fs_node_id (&chi_id, txn_root, "/A/D/H/chi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "chi", pool)); |
| SVN_ERR (svn_fs_node_id (&psi_id, txn_root, "/A/D/H/psi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "psi", pool)); |
| SVN_ERR (svn_fs_node_id (&omega_id, txn_root, "/A/D/H/omega", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "omega", pool)); |
| SVN_ERR (svn_fs_node_id (&G_id, txn_root, "/A/D/G", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "G", pool)); |
| SVN_ERR (svn_fs_node_id (&pi_id, txn_root, "/A/D/G/pi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "pi", pool)); |
| SVN_ERR (svn_fs_node_id (&rho_id, txn_root, "/A/D/G/rho", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "rho", pool)); |
| SVN_ERR (svn_fs_node_id (&tau_id, txn_root, "/A/D/G/tau", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| SVN_ERR (svn_fs_node_id (&sigma_id, txn_root, "/A/D/G/sigma", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "sigma", pool)); |
| |
| /* First try an unsuccessful delete. */ |
| err = svn_fs_delete (txn_root, "A", pool); |
| if (err && (err->apr_err != SVN_ERR_FS_DIR_NOT_EMPTY)) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "delete failed as expected, but for wrong reason"); |
| } |
| else if (! err) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "delete succeeded when expected to fail"); |
| } |
| |
| /* Then try a successful delete. */ |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "", "A", pool)); |
| SVN_ERR (check_id_absent (fs, A_id, pool)); |
| SVN_ERR (check_id_present (fs, mu_id, pool)); |
| SVN_ERR (check_id_present (fs, B_id, pool)); |
| SVN_ERR (check_id_present (fs, lambda_id, pool)); |
| SVN_ERR (check_id_present (fs, E_id, pool)); |
| SVN_ERR (check_id_present (fs, alpha_id, pool)); |
| SVN_ERR (check_id_present (fs, beta_id, pool)); |
| SVN_ERR (check_id_present (fs, F_id, pool)); |
| SVN_ERR (check_id_present (fs, C_id, pool)); |
| SVN_ERR (check_id_absent (fs, D_id, pool)); |
| SVN_ERR (check_id_present (fs, gamma_id, pool)); |
| SVN_ERR (check_id_present (fs, H_id, pool)); |
| SVN_ERR (check_id_present (fs, chi_id, pool)); |
| SVN_ERR (check_id_present (fs, psi_id, pool)); |
| SVN_ERR (check_id_present (fs, omega_id, pool)); |
| SVN_ERR (check_id_absent (fs, G_id, pool)); |
| SVN_ERR (check_id_present (fs, pi_id, pool)); |
| SVN_ERR (check_id_present (fs, rho_id, pool)); |
| SVN_ERR (check_id_present (fs, tau_id, pool)); |
| SVN_ERR (check_id_absent (fs, sigma_id, pool)); |
| |
| /* Validate the tree. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" } |
| }; |
| |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 1, pool)); |
| } |
| } |
| |
| /* Abort transaction. */ |
| SVN_ERR (svn_fs_abort_txn (txn)); |
| |
| /* 4. Delete immutable file. */ |
| |
| /* Create new transaction. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, new_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| { |
| svn_fs_id_t *iota_id, *gamma_id; |
| |
| /* Check nodes revision ID is present. */ |
| SVN_ERR (svn_fs_node_id (&iota_id, txn_root, "iota", pool)); |
| SVN_ERR (svn_fs_node_id (&gamma_id, txn_root, "A/D/gamma", pool)); |
| SVN_ERR (check_entry_present (txn_root, "", "iota", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (check_id_present (fs, iota_id, pool)); |
| SVN_ERR (check_id_present (fs, gamma_id, pool)); |
| |
| /* Try it once with plain delete(). */ |
| SVN_ERR (svn_fs_delete (txn_root, "iota", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "", "iota", pool)); |
| SVN_ERR (check_id_present (fs, iota_id, pool)); |
| |
| /* Try it once with delete_tree(). */ |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A/D/gamma", pool)); |
| SVN_ERR (check_entry_absent (txn_root, "A/D", "iota", pool)); |
| SVN_ERR (check_id_present (fs, gamma_id, pool)); |
| |
| /* Validate the tree. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 18, pool)); |
| } |
| } |
| |
| /* Abort transaction. */ |
| SVN_ERR (svn_fs_abort_txn (txn)); |
| |
| /* 5. Delete immutable directory. */ |
| |
| /* Create new transaction. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, new_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| |
| { |
| svn_fs_id_t *A_id, *mu_id, *B_id, *lambda_id, *E_id, *alpha_id, |
| *beta_id, *F_id, *C_id, *D_id, *gamma_id, *H_id, *chi_id, |
| *psi_id, *omega_id, *G_id, *pi_id, *rho_id, *tau_id; |
| |
| /* Check nodes revision ID is present. */ |
| SVN_ERR (svn_fs_node_id (&A_id, txn_root, "/A", pool)); |
| SVN_ERR (check_entry_present (txn_root, "", "A", pool)); |
| SVN_ERR (svn_fs_node_id (&mu_id, txn_root, "/A/mu", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "mu", pool)); |
| SVN_ERR (svn_fs_node_id (&B_id, txn_root, "/A/B", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "B", pool)); |
| SVN_ERR (svn_fs_node_id (&lambda_id, txn_root, "/A/B/lambda", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "lambda", pool)); |
| SVN_ERR (svn_fs_node_id (&E_id, txn_root, "/A/B/E", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "E", pool)); |
| SVN_ERR (svn_fs_node_id (&alpha_id, txn_root, "/A/B/E/alpha", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B/E", "alpha", pool)); |
| SVN_ERR (svn_fs_node_id (&beta_id, txn_root, "/A/B/E/beta", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B/E", "beta", pool)); |
| SVN_ERR (svn_fs_node_id (&F_id, txn_root, "/A/B/F", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/B", "F", pool)); |
| SVN_ERR (svn_fs_node_id (&C_id, txn_root, "/A/C", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "C", pool)); |
| SVN_ERR (svn_fs_node_id (&D_id, txn_root, "/A/D", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A", "D", pool)); |
| SVN_ERR (svn_fs_node_id (&gamma_id, txn_root, "/A/D/gamma", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "gamma", pool)); |
| SVN_ERR (svn_fs_node_id (&H_id, txn_root, "/A/D/H", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "H", pool)); |
| SVN_ERR (svn_fs_node_id (&chi_id, txn_root, "/A/D/H/chi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "chi", pool)); |
| SVN_ERR (svn_fs_node_id (&psi_id, txn_root, "/A/D/H/psi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "psi", pool)); |
| SVN_ERR (svn_fs_node_id (&omega_id, txn_root, "/A/D/H/omega", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/H", "omega", pool)); |
| SVN_ERR (svn_fs_node_id (&G_id, txn_root, "/A/D/G", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D", "G", pool)); |
| SVN_ERR (svn_fs_node_id (&pi_id, txn_root, "/A/D/G/pi", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "pi", pool)); |
| SVN_ERR (svn_fs_node_id (&rho_id, txn_root, "/A/D/G/rho", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "rho", pool)); |
| SVN_ERR (svn_fs_node_id (&tau_id, txn_root, "/A/D/G/tau", pool)); |
| SVN_ERR (check_entry_present (txn_root, "A/D/G", "tau", pool)); |
| |
| /* First try an unsuccessful delete. */ |
| err = svn_fs_delete (txn_root, "A", pool); |
| if (err && (err->apr_err != SVN_ERR_FS_DIR_NOT_EMPTY)) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "delete failed as expected, but for wrong reason"); |
| } |
| else if (! err) |
| { |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "delete succeeded when expected to fail"); |
| } |
| |
| /* Then try a successful delete. */ |
| SVN_ERR (svn_fs_delete_tree (txn_root, "A", pool)); |
| |
| SVN_ERR (check_entry_absent (txn_root, "", "A", pool)); |
| SVN_ERR (check_id_present (fs, A_id, pool)); |
| SVN_ERR (check_id_present (fs, mu_id, pool)); |
| SVN_ERR (check_id_present (fs, B_id, pool)); |
| SVN_ERR (check_id_present (fs, lambda_id, pool)); |
| SVN_ERR (check_id_present (fs, E_id, pool)); |
| SVN_ERR (check_id_present (fs, alpha_id, pool)); |
| SVN_ERR (check_id_present (fs, beta_id, pool)); |
| SVN_ERR (check_id_present (fs, F_id, pool)); |
| SVN_ERR (check_id_present (fs, C_id, pool)); |
| SVN_ERR (check_id_present (fs, D_id, pool)); |
| SVN_ERR (check_id_present (fs, gamma_id, pool)); |
| SVN_ERR (check_id_present (fs, H_id, pool)); |
| SVN_ERR (check_id_present (fs, chi_id, pool)); |
| SVN_ERR (check_id_present (fs, psi_id, pool)); |
| SVN_ERR (check_id_present (fs, omega_id, pool)); |
| SVN_ERR (check_id_present (fs, G_id, pool)); |
| SVN_ERR (check_id_present (fs, pi_id, pool)); |
| SVN_ERR (check_id_present (fs, rho_id, pool)); |
| SVN_ERR (check_id_present (fs, tau_id, pool)); |
| |
| /* Validate the tree. */ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" } |
| }; |
| SVN_ERR (svn_test__validate_tree (txn_root, expected_entries, 1, pool)); |
| } |
| } |
| |
| /* Close the transaction and fs. */ |
| SVN_ERR (svn_fs_close_txn (txn)); |
| SVN_ERR (svn_fs_close_fs (fs)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Test the datestamps on commits. */ |
| static svn_error_t * |
| commit_date (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_revnum_t rev; |
| svn_string_t *datestamp; |
| apr_time_t before_commit, at_commit, after_commit; |
| |
| *msg = "commit datestamps"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-commit-date", pool)); |
| |
| before_commit = apr_time_now (); |
| |
| /* Commit a greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| after_commit = apr_time_now (); |
| |
| /* Get the datestamp of the commit. */ |
| SVN_ERR (svn_fs_revision_prop (&datestamp, fs, rev, SVN_PROP_REVISION_DATE, |
| pool)); |
| |
| if (datestamp == NULL) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "failed to get datestamp of committed revision"); |
| |
| at_commit = svn_time_from_nts (datestamp->data); |
| |
| if (at_commit < before_commit) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "datestamp too early"); |
| |
| if (at_commit > after_commit) |
| return svn_error_create |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "datestamp too late"); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| check_old_revisions (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_revnum_t rev; |
| |
| *msg = "check old revisions"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Prepare a filesystem. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-check-old-revisions", pool)); |
| |
| /* Commit a greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Modify and commit iota a few times, then test to see if we can |
| retrieve all the committed revisions. */ |
| { |
| /* right-side numbers match revision numbers */ |
| #define iota_contents_1 "This is the file 'iota'.\n" |
| |
| /* Add a char to the front. */ |
| #define iota_contents_2 "XThis is the file 'iota'.\n" |
| |
| /* Add a char to the end. */ |
| #define iota_contents_3 "XThis is the file 'iota'.\nX" |
| |
| /* Add a couple of chars in the middle. */ |
| #define iota_contents_4 "XThis is the X file 'iota'.\nX" |
| |
| /* Randomly add and delete chars all over. */ |
| #define iota_contents_5 \ |
| "XTYhQis is ACK, PHHHT! no longer 'ioZZZZZta'.blarf\nbye" |
| |
| /* Reassure iota that it will live for quite some time. */ |
| #define iota_contents_6 "Matthew 5:18 (Revised Standard Version) --\n\ |
| For truly, I say to you, till heaven and earth pass away, not an iota,\n\ |
| not a dot, will pass from the law until all is accomplished." |
| |
| /* Revert to the original contents. */ |
| #define iota_contents_7 "This is the file 'iota'.\n" |
| |
| /* Revision 2. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", iota_contents_2, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Revision 3. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", iota_contents_3, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Revision 4. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", iota_contents_4, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Revision 5. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", iota_contents_5, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Revision 6. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", iota_contents_6, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Revision 7. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", iota_contents_7, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /** Now check the full Greek Tree in all of those revisions, |
| adjusting `iota' for each one. ***/ |
| |
| /* Validate revision 1. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_1 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 1, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| |
| /* Validate revision 2. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_2 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 2, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| |
| /* Validate revision 3. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_3 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 3, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| |
| /* Validate revision 4. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_4 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 4, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| |
| /* Validate revision 5. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_5 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/G", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 5, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| |
| /* Validate revision 6. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_6 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 6, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| |
| /* Validate revision 7. */ |
| { |
| svn_fs_root_t *root; |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", iota_contents_7 }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| |
| SVN_ERR (svn_fs_revision_root (&root, fs, 7, pool)); |
| SVN_ERR (svn_test__validate_tree (root, expected_entries, 20, pool)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* For each revision R in FS, from 0 to (NUM_REVS - 1), check that it |
| matches the tree in EXPECTED_TREES[R]. Use POOL for any |
| allocations. This is a helper function for check_all_revisions(). */ |
| static svn_error_t * |
| validate_revisions (svn_fs_t *fs, |
| svn_test__tree_t *expected_trees, |
| int num_revs, |
| apr_pool_t *pool) |
| { |
| svn_fs_root_t *revision_root; |
| int i; |
| svn_error_t *err; |
| apr_pool_t *subpool = svn_pool_create (pool); |
| |
| /* Validate all revisions up to the current one. */ |
| for (i = 0; i < num_revs; i++) |
| { |
| SVN_ERR (svn_fs_revision_root (&revision_root, fs, |
| (svn_revnum_t)i, subpool)); |
| err = svn_test__validate_tree (revision_root, |
| expected_trees[i].entries, |
| expected_trees[i].num_entries, |
| subpool); |
| if (err) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, err, pool, |
| "Error validating revision %lu (youngest is %lu)", |
| (long unsigned int) i, (long unsigned int) (num_revs - 1)); |
| |
| svn_pool_clear (subpool); |
| } |
| |
| svn_pool_destroy (subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| check_all_revisions (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root; |
| svn_revnum_t youngest_rev; |
| svn_test__tree_t expected_trees[5]; /* one tree per commit, please */ |
| int revision_count = 0; |
| |
| *msg = "after each commit, check all revisions"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-check-all-revisions", pool)); |
| |
| /***********************************************************************/ |
| /* REVISION 0 */ |
| /***********************************************************************/ |
| { |
| expected_trees[revision_count].num_entries = 0; |
| expected_trees[revision_count++].entries = 0; |
| SVN_ERR (validate_revisions (fs, expected_trees, revision_count, pool)); |
| } |
| |
| /* Create and commit the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /***********************************************************************/ |
| /* REVISION 1 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "This is the file 'iota'.\n" }, |
| { "A", 0 }, |
| { "A/mu", "This is the file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/C", 0 }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "This is the file 'rho'.\n" }, |
| { "A/D/G/tau", "This is the file 'tau'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", "This is the file 'omega'.\n" } |
| }; |
| expected_trees[revision_count].entries = expected_entries; |
| expected_trees[revision_count++].num_entries = 20; |
| SVN_ERR (validate_revisions (fs, expected_trees, revision_count, pool)); |
| } |
| |
| /* Make a new txn based on the youngest revision, make some changes, |
| and commit those changes (which makes a new youngest |
| revision). */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "A/delta", "This is the file 'delta'.\n" }, |
| { 'a', "A/epsilon", "This is the file 'epsilon'.\n" }, |
| { 'a', "A/B/Z", 0 }, |
| { 'a', "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
| { 'd', "A/C", 0 }, |
| { 'd', "A/mu" "" }, |
| { 'd', "A/D/G/tau", "" }, |
| { 'd', "A/D/H/omega", "" }, |
| { 'e', "iota", "Changed file 'iota'.\n" }, |
| { 'e', "A/D/G/rho", "Changed file 'rho'.\n" } |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 10, pool)); |
| } |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /***********************************************************************/ |
| /* REVISION 2 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "iota", "Changed file 'iota'.\n" }, |
| { "A", 0 }, |
| { "A/delta", "This is the file 'delta'.\n" }, |
| { "A/epsilon", "This is the file 'epsilon'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/B/Z", 0 }, |
| { "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "Changed file 'rho'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" } |
| }; |
| expected_trees[revision_count].entries = expected_entries; |
| expected_trees[revision_count++].num_entries = 20; |
| SVN_ERR (validate_revisions (fs, expected_trees, revision_count, pool)); |
| } |
| |
| /* Make a new txn based on the youngest revision, make some changes, |
| and commit those changes (which makes a new youngest |
| revision). */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "A/mu", "Re-added file 'mu'.\n" }, |
| { 'a', "A/D/H/omega", 0 }, /* re-add omega as directory! */ |
| { 'd', "iota", "" }, |
| { 'e', "A/delta", "This is the file 'delta'.\nLine 2.\n" } |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 4, pool)); |
| } |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /***********************************************************************/ |
| /* REVISION 3 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "A", 0 }, |
| { "A/delta", "This is the file 'delta'.\nLine 2.\n" }, |
| { "A/epsilon", "This is the file 'epsilon'.\n" }, |
| { "A/mu", "Re-added file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/B/Z", 0 }, |
| { "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "Changed file 'rho'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", 0 } |
| }; |
| expected_trees[revision_count].entries = expected_entries; |
| expected_trees[revision_count++].num_entries = 21; |
| SVN_ERR (validate_revisions (fs, expected_trees, revision_count, pool)); |
| } |
| |
| /* Make a new txn based on the youngest revision, make some changes, |
| and commit those changes (which makes a new youngest |
| revision). */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| { |
| svn_test__txn_script_command_t script_entries[] = { |
| { 'c', "A/D/G", "A/D/G2" }, |
| { 'c', "A/epsilon", "A/B/epsilon" }, |
| }; |
| SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 2, pool)); |
| } |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /***********************************************************************/ |
| /* REVISION 4 */ |
| /***********************************************************************/ |
| { |
| svn_test__tree_entry_t expected_entries[] = { |
| /* path, contents (0 = dir) */ |
| { "A", 0 }, |
| { "A/delta", "This is the file 'delta'.\nLine 2.\n" }, |
| { "A/epsilon", "This is the file 'epsilon'.\n" }, |
| { "A/mu", "Re-added file 'mu'.\n" }, |
| { "A/B", 0 }, |
| { "A/B/epsilon", "This is the file 'epsilon'.\n" }, |
| { "A/B/lambda", "This is the file 'lambda'.\n" }, |
| { "A/B/E", 0 }, |
| { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
| { "A/B/E/beta", "This is the file 'beta'.\n" }, |
| { "A/B/F", 0 }, |
| { "A/B/Z", 0 }, |
| { "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
| { "A/D", 0 }, |
| { "A/D/gamma", "This is the file 'gamma'.\n" }, |
| { "A/D/G", 0 }, |
| { "A/D/G/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G/rho", "Changed file 'rho'.\n" }, |
| { "A/D/G2", 0 }, |
| { "A/D/G2/pi", "This is the file 'pi'.\n" }, |
| { "A/D/G2/rho", "Changed file 'rho'.\n" }, |
| { "A/D/H", 0 }, |
| { "A/D/H/chi", "This is the file 'chi'.\n" }, |
| { "A/D/H/psi", "This is the file 'psi'.\n" }, |
| { "A/D/H/omega", 0 } |
| }; |
| expected_trees[revision_count].entries = expected_entries; |
| expected_trees[revision_count++].num_entries = 25; |
| SVN_ERR (validate_revisions (fs, expected_trees, revision_count, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Helper function for large_file_integrity(). Given a ROOT and PATH |
| to a file, calculate and return the MD5 digest for the contents of |
| the file. */ |
| static svn_error_t * |
| get_file_digest (unsigned char digest[MD5_DIGESTSIZE], |
| svn_fs_root_t *root, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *stream; |
| apr_size_t len; |
| apr_md5_ctx_t context; |
| |
| /* ### todo: Pool usage in svndiff is currently really, really |
| crappy. We need to keep this buffer fairly large so we don't run |
| out of memory doing undeltification of large files into tiny |
| buffers. Issue #465. */ |
| char buf[100000]; |
| |
| /* Get a stream for the file contents. */ |
| SVN_ERR (svn_fs_file_contents (&stream, root, path, pool)); |
| |
| /* Initialize APR MD5 context. */ |
| apr_md5_init (&context); |
| |
| do |
| { |
| /* "please fill the buf with bytes" */ |
| len = sizeof (buf); |
| SVN_ERR (svn_stream_read (stream, buf, &len)); |
| |
| /* Update the MD5 calculation with the data we just read. */ |
| apr_md5_update (&context, buf, len); |
| |
| } while (len == sizeof (buf)); /* Continue until a short read. */ |
| |
| /* Finalize MD5 calculation. */ |
| apr_md5_final (digest, &context); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static int my_rand (int scalar) |
| { |
| return (int)(((float)rand() / (float)RAND_MAX) * (float)scalar); |
| } |
| |
| |
| /* Put pseudo-random bytes in buffer BUF (which is LEN bytes long). |
| If FULL is TRUE, simply replace every byte in BUF with a |
| pseudo-random byte, else, replace a pseudo-random collection of |
| bytes with pseudo-random data. */ |
| static void |
| random_data_to_buffer (char *buf, |
| apr_size_t buf_len, |
| svn_boolean_t full) |
| { |
| apr_size_t i; |
| apr_size_t num_bytes; |
| apr_size_t offset; |
| |
| int ds_off = 0; |
| char dataset[10] = "0123456789"; |
| int dataset_size = strlen (dataset); |
| |
| if (full) |
| { |
| for (i = 0; i < buf_len; i++) |
| { |
| ds_off = my_rand (dataset_size); |
| buf[i] = dataset[ds_off]; |
| } |
| |
| return; |
| } |
| |
| num_bytes = my_rand (buf_len / 100) + 1; |
| for (i = 0; i < num_bytes; i++) |
| { |
| offset = my_rand (buf_len - 1); |
| ds_off = my_rand (dataset_size); |
| buf[offset] = dataset[ds_off]; |
| } |
| |
| return; |
| } |
| |
| |
| static svn_error_t * |
| file_integrity_helper (apr_size_t filesize, apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t youngest_rev = 0; |
| apr_pool_t *subpool = svn_pool_create (pool); |
| svn_string_t contents; |
| char *content_buffer; |
| unsigned char digest[MD5_DIGESTSIZE]; |
| unsigned char digest_list[100][MD5_DIGESTSIZE]; |
| svn_txdelta_window_handler_t wh_func; |
| void *wh_baton; |
| int j; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-large-file-integrity", pool)); |
| |
| /* Set up our file contents string buffer. */ |
| content_buffer = apr_palloc (pool, filesize); |
| |
| contents.data = content_buffer; |
| contents.len = filesize; |
| |
| /* THE PLAN: |
| |
| The plan here is simple. We have a very large file (FILESIZE |
| bytes) that we initialize with pseudo-random data and commit. |
| Then we make pseudo-random modifications to that file's contents, |
| committing after each mod. Prior to each commit, we generate an |
| MD5 checksum for the contents of the file, storing each of those |
| checksums in an array. After we've made a whole bunch of edits |
| and commits, we'll re-check that file's contents as of each |
| revision in the repository, recalculate a checksum for those |
| contents, and make sure the "before" and "after" checksums |
| match. */ |
| |
| /* Create a big, ugly, pseudo-random-filled file and commit it. */ |
| svn_pool_clear (subpool); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "bigfile", subpool)); |
| random_data_to_buffer (content_buffer, filesize, TRUE); |
| apr_md5 (digest, contents.data, contents.len); |
| SVN_ERR (svn_fs_apply_textdelta |
| (&wh_func, &wh_baton, txn_root, "bigfile", subpool)); |
| SVN_ERR (svn_txdelta_send_string (&contents, wh_func, wh_baton, subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| memcpy (digest_list[youngest_rev], digest, MD5_DIGESTSIZE); |
| |
| /* Now, let's make some edits to the beginning of our file, and |
| commit those. */ |
| svn_pool_clear (subpool); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| random_data_to_buffer (content_buffer, 20, TRUE); |
| apr_md5 (digest, contents.data, contents.len); |
| SVN_ERR (svn_fs_apply_textdelta |
| (&wh_func, &wh_baton, txn_root, "bigfile", subpool)); |
| SVN_ERR (svn_txdelta_send_string (&contents, wh_func, wh_baton, subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| memcpy (digest_list[youngest_rev], digest, MD5_DIGESTSIZE); |
| |
| /* Now, let's make some edits to the end of our file. */ |
| svn_pool_clear (subpool); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| random_data_to_buffer (content_buffer + (filesize - 20), 20, TRUE); |
| apr_md5 (digest, contents.data, contents.len); |
| SVN_ERR (svn_fs_apply_textdelta |
| (&wh_func, &wh_baton, txn_root, "bigfile", subpool)); |
| SVN_ERR (svn_txdelta_send_string (&contents, wh_func, wh_baton, subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| memcpy (digest_list[youngest_rev], digest, MD5_DIGESTSIZE); |
| |
| /* How about some edits to both the beginning and the end of the |
| file? */ |
| svn_pool_clear (subpool); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| random_data_to_buffer (content_buffer, 20, TRUE); |
| random_data_to_buffer (content_buffer + (filesize - 20), 20, TRUE); |
| apr_md5 (digest, contents.data, contents.len); |
| SVN_ERR (svn_fs_apply_textdelta |
| (&wh_func, &wh_baton, txn_root, "bigfile", subpool)); |
| SVN_ERR (svn_txdelta_send_string (&contents, wh_func, wh_baton, subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| memcpy (digest_list[youngest_rev], digest, MD5_DIGESTSIZE); |
| |
| /* Alright, now we're just going to go crazy. Let's make many more |
| edits -- pseudo-random numbers and offsets of bytes changed to |
| more pseudo-random values. */ |
| for (j = youngest_rev; j < 30; j = youngest_rev) |
| { |
| svn_pool_clear (subpool); |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| random_data_to_buffer (content_buffer, filesize, FALSE); |
| apr_md5 (digest, contents.data, contents.len); |
| SVN_ERR (svn_fs_apply_textdelta |
| (&wh_func, &wh_baton, txn_root, "bigfile", subpool)); |
| SVN_ERR (svn_txdelta_send_string |
| (&contents, wh_func, wh_baton, subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| memcpy (digest_list[youngest_rev], digest, MD5_DIGESTSIZE); |
| } |
| |
| /* Now, calculate an MD5 digest for the contents of our big ugly |
| file in each revision currently in existence, and make the sure |
| the checksum matches the checksum of the data prior to its |
| commit. */ |
| for (j = youngest_rev; j > 0; j--) |
| { |
| svn_pool_clear (subpool); |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, j, subpool)); |
| SVN_ERR (get_file_digest (digest, rev_root, "bigfile", subpool)); |
| if (memcmp (digest, digest_list[j], MD5_DIGESTSIZE)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "MD5 checksum failure, revision %lu", (long unsigned int)j); |
| } |
| |
| svn_pool_destroy (subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| medium_file_integrity (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| *msg = "create and modify a medium file, verifying its integrity"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Being no larger than the standard delta window size affects |
| deltification internally, so test that. */ |
| return file_integrity_helper (SVN_STREAM_CHUNK_SIZE, pool); |
| } |
| |
| |
| static svn_error_t * |
| large_file_integrity (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| *msg = "create and modify a large file, verifying its integrity"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Being larger than the standard delta window size affects |
| deltification internally, so test that. */ |
| return file_integrity_helper (SVN_STREAM_CHUNK_SIZE + 1, pool); |
| } |
| |
| |
| struct get_node_revision_args |
| { |
| svn_fs_t *fs; |
| svn_fs_id_t *id; |
| skel_t *node_rev; |
| }; |
| |
| |
| static svn_error_t * |
| txn_body_get_node_revision (void *baton, trail_t *trail) |
| { |
| struct get_node_revision_args *args = baton; |
| |
| return svn_fs__get_node_revision (&(args->node_rev), args->fs, |
| args->id, trail); |
| } |
| |
| |
| static svn_error_t * |
| check_root_revision (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t youngest_rev, test_rev; |
| struct get_node_revision_args args; |
| int i; |
| |
| *msg = "make sure the root node's stored revision is accurate"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-check-root-revision", pool)); |
| |
| /* Initialize this once for all time. */ |
| args.fs = fs; |
| |
| /* Create and commit the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, pool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Root node's revision should be the same as YOUNGEST_REV. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, pool)); |
| SVN_ERR (svn_fs_node_id (&args.id, rev_root, "", pool)); |
| SVN_ERR (svn_fs__retry_txn (fs, txn_body_get_node_revision, &args, pool)); |
| test_rev = SVN_STR_TO_REV ((SVN_FS__NR_HDR_REV |
| (SVN_FS__NR_HEADER (args.node_rev)))->data); |
| if (test_rev != youngest_rev) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "Root node in revision %lu has unexpected stored revision %lu", |
| youngest_rev, test_rev); |
| |
| for (i = 0; i < 10; i++) |
| { |
| /* Create and commit the greek tree. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool)); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", |
| apr_psprintf (pool, "iota version %d", i + 2), pool)); |
| |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Root node's revision should be the same as YOUNGEST_REV. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, pool)); |
| SVN_ERR (svn_fs_node_id (&args.id, rev_root, "", pool)); |
| SVN_ERR (svn_fs__retry_txn (fs, txn_body_get_node_revision, |
| &args, pool)); |
| test_rev = SVN_STR_TO_REV ((SVN_FS__NR_HDR_REV |
| (SVN_FS__NR_HEADER (args.node_rev)))->data); |
| if (test_rev != youngest_rev) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "Root node in revision %lu has unexpected stored revision %lu", |
| youngest_rev, test_rev); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| undeltify_deltify (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t youngest_rev = 0, i_rev; |
| int i; |
| apr_pool_t *subpool, *iterpool; |
| const char *greek_files[12][1 + 10] = { |
| /* name per-rev contents ... */ |
| { "iota", 0 }, |
| { "A/mu", 0 }, |
| { "A/B/lambda", 0 }, |
| { "A/B/E/alpha", 0 }, |
| { "A/B/E/beta", 0 }, |
| { "A/D/gamma", 0 }, |
| { "A/D/G/pi", 0 }, |
| { "A/D/G/rho", 0 }, |
| { "A/D/G/tau", 0 }, |
| { "A/D/H/chi", 0 }, |
| { "A/D/H/psi", 0 }, |
| { "A/D/H/omega", 0 } |
| }; |
| |
| *msg = "pound on the filesystem's explicit (un-)deltification code"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-undeltify-deltify", pool)); |
| |
| /* Make 10 revisions. */ |
| subpool = svn_pool_create (pool); |
| while (youngest_rev < 10) |
| { |
| /* Start the next transaction */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| |
| /* The first time through, create the Greek tree. */ |
| if (youngest_rev == 0) |
| SVN_ERR (svn_test__create_greek_tree (txn_root, subpool)); |
| |
| /* Modify each file (changing all bytes only the first time). */ |
| for (i = 0; i < 12; i++) |
| { |
| const char *path = greek_files[i][0]; |
| char buf[1025]; |
| |
| random_data_to_buffer (buf, 1024, youngest_rev ? FALSE : TRUE); |
| buf[1024] = 0; |
| greek_files[i][youngest_rev + 1] = apr_pstrdup (pool, buf); |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, path, greek_files[i][youngest_rev + 1], |
| subpool)); |
| } |
| |
| /* Commit the mods. */ |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Clear out the per-file pool. */ |
| svn_pool_clear (subpool); |
| } |
| |
| /* Now, undeltify each file, in each revision (starting with the |
| youngest, and going backward to revision 0), verifying that its |
| contents are as expected. */ |
| for (i_rev = youngest_rev; i_rev; i_rev--) |
| { |
| /* Get a revision root. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, i_rev, subpool)); |
| |
| /* Create a per-iteration subpool. */ |
| iterpool = svn_pool_create (subpool); |
| |
| /* Loop over files, undeltifying (then verifying) their |
| contents, then re-deltifying (and verifying) them again. */ |
| for (i = 0; i < 12; i++) |
| { |
| const char *path = greek_files[i][0]; |
| svn_stringbuf_t *contents; |
| |
| /* Undeltify this file, get its contents, and make sure they |
| 'check out'. */ |
| SVN_ERR (svn_fs_undeltify (rev_root, path, 0, iterpool)); |
| SVN_ERR (svn_test__get_file_contents (rev_root, path, &contents, |
| iterpool)); |
| if (strcmp (greek_files[i][i_rev], contents->data)) |
| return svn_error_createf |
| (SVN_ERR_FS_CORRUPT, 0, NULL, pool, |
| "%s:%ld undeltified contents are incorrect", path, i_rev); |
| |
| /* Now, re-deltify, and again get its contents and verify them. */ |
| SVN_ERR (svn_fs_deltify (rev_root, path, 0, iterpool)); |
| SVN_ERR (svn_test__get_file_contents (rev_root, path, &contents, |
| iterpool)); |
| if (strcmp (greek_files[i][i_rev], contents->data)) |
| return svn_error_createf |
| (SVN_ERR_FS_CORRUPT, 0, NULL, pool, |
| "%s:%ld re-deltified contents are incorrect", path, i_rev); |
| |
| /* Clear out the per-file pool. */ |
| svn_pool_clear (iterpool); |
| } |
| |
| /* Destroy the per-file pool. */ |
| svn_pool_destroy (iterpool); |
| |
| /* Clear out the per-revision pool. */ |
| svn_pool_clear (subpool); |
| } |
| |
| |
| /*** Now, let's test the recursive deltification/undeltification. ***/ |
| |
| /* Undeltify... */ |
| for (i_rev = youngest_rev; i_rev; i_rev--) |
| { |
| /* Un-deltify (recursively) the root directory in this revision. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, i_rev, subpool)); |
| SVN_ERR (svn_fs_undeltify (rev_root, "", 1, subpool)); |
| |
| /* Create per-iteration subpool. */ |
| iterpool = svn_pool_create (subpool); |
| |
| /* Verify file contents are all good. */ |
| for (i = 0; i < 12; i++) |
| { |
| svn_stringbuf_t *contents; |
| const char *path = greek_files[i][0]; |
| |
| /* Verify file contents. */ |
| SVN_ERR (svn_test__get_file_contents (rev_root, path, &contents, |
| iterpool)); |
| if (strcmp (greek_files[i][i_rev], contents->data)) |
| return svn_error_createf |
| (SVN_ERR_FS_CORRUPT, 0, NULL, pool, |
| "%s:%ld undeltified contents are incorrect", path, i_rev); |
| |
| /* Clear out the per-file pool. */ |
| svn_pool_clear (iterpool); |
| } |
| |
| /* Putting chlorine in one algae-filled (sub)pool, which |
| completely drains the other (iterpool)... */ |
| svn_pool_clear (subpool); |
| } |
| |
| /* And re-deltify... */ |
| for (i_rev = 1; i_rev <= youngest_rev; i_rev++) |
| { |
| /* Un-deltify (recursively) the root directory in this revision. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, i_rev, subpool)); |
| SVN_ERR (svn_fs_deltify (rev_root, "", 1, subpool)); |
| |
| /* Create per-iteration subpool. */ |
| iterpool = svn_pool_create (subpool); |
| |
| /* Verify file contents are all good. */ |
| for (i = 0; i < 12; i++) |
| { |
| svn_stringbuf_t *contents; |
| const char *path = greek_files[i][0]; |
| |
| /* Verify file contents. */ |
| SVN_ERR (svn_test__get_file_contents (rev_root, path, &contents, |
| iterpool)); |
| if (strcmp (greek_files[i][i_rev], contents->data)) |
| return svn_error_createf |
| (SVN_ERR_FS_CORRUPT, 0, NULL, pool, |
| "%s:%ld re-deltified contents are incorrect", path, i_rev); |
| |
| /* Clear out the per-file pool. */ |
| svn_pool_clear (iterpool); |
| } |
| |
| /* Putting chlorine in one algae-filled (sub)pool, which |
| completely drains the other (iterpool)... */ |
| svn_pool_clear (subpool); |
| } |
| |
| /* Destroy the per-revision pool. */ |
| svn_pool_destroy (subpool); |
| |
| /* Close the filesystem. */ |
| svn_fs_close_fs (fs); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct node_created_rev_args { |
| const char *path; |
| svn_revnum_t rev; |
| }; |
| |
| |
| static svn_error_t * |
| verify_path_revs (svn_fs_root_t *root, |
| struct node_created_rev_args *args, |
| int num_path_revs, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create (pool); |
| int i; |
| svn_revnum_t rev; |
| |
| for (i = 0; i < num_path_revs; i++) |
| { |
| SVN_ERR (svn_fs_node_created_rev (&rev, root, args[i].path, subpool)); |
| if (rev != args[i].rev) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, pool, |
| "verify_path_revs: '%s' has created rev '%ld' (expected '%ld')", |
| args[i].path, rev, args[i].rev); |
| } |
| |
| apr_pool_destroy (subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| test_node_created_rev (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create (pool); |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t youngest_rev = 0; |
| int i; |
| struct node_created_rev_args path_revs[21]; |
| const char *greek_paths[21] = { |
| /* 0 */ "", |
| /* 1 */ "iota", |
| /* 2 */ "A", |
| /* 3 */ "A/mu", |
| /* 4 */ "A/B", |
| /* 5 */ "A/B/lambda", |
| /* 6 */ "A/B/E", |
| /* 7 */ "A/B/E/alpha", |
| /* 8 */ "A/B/E/beta", |
| /* 9 */ "A/B/F", |
| /* 10 */ "A/C", |
| /* 11 */ "A/D", |
| /* 12 */ "A/D/gamma", |
| /* 13 */ "A/D/G", |
| /* 14 */ "A/D/G/pi", |
| /* 15 */ "A/D/G/rho", |
| /* 16 */ "A/D/G/tau", |
| /* 17 */ "A/D/H", |
| /* 18 */ "A/D/H/chi", |
| /* 19 */ "A/D/H/psi", |
| /* 20 */ "A/D/H/omega", |
| }; |
| |
| *msg = "svn_fs_node_created_rev test"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Initialize the paths in our args list. */ |
| for (i = 0; i < 20; i++) |
| path_revs[i].path = greek_paths[i]; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-node-created-rev", pool)); |
| |
| /* Created the greek tree in revision 1. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, subpool)); |
| |
| /* Now, prior to committing, all these nodes should have an invalid |
| created rev. After all, the rev has been created yet. Verify |
| this. */ |
| for (i = 0; i < 20; i++) |
| path_revs[i].rev = SVN_INVALID_REVNUM; |
| SVN_ERR (verify_path_revs (txn_root, path_revs, 20, subpool)); |
| |
| /* Now commit the transaction. */ |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /* Now, we have a new revision, and all paths in it should have a |
| created rev of 1. Verify this. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, subpool)); |
| for (i = 0; i < 20; i++) |
| path_revs[i].rev = 1; |
| SVN_ERR (verify_path_revs (rev_root, path_revs, 20, subpool)); |
| |
| /*** Let's make some changes/commits here and there, and make sure |
| we can keep this whole created rev thing in good standing. The |
| general rule here is that prior to commit, mutable things have |
| an invalid created rev, immutable things have their original |
| created rev. After the commit, those things which had invalid |
| created revs in the transaction now have the youngest revision |
| as their created rev. |
| |
| ### NOTE: Bubble-up currently affect the created revisions for |
| directory nodes. I'm not sure if this is the behavior we've |
| settled on as desired. |
| */ |
| |
| /*** clear the per-commit pool */ |
| svn_pool_clear (subpool); |
| /* begin a new transaction */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| /* make mods */ |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "iota", "pointless mod here", subpool)); |
| /* verify created revs */ |
| path_revs[0].rev = SVN_INVALID_REVNUM; /* (root) */ |
| path_revs[1].rev = SVN_INVALID_REVNUM; /* iota */ |
| SVN_ERR (verify_path_revs (txn_root, path_revs, 20, subpool)); |
| /* commit transaction */ |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| /* get a revision root for the new revision */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, subpool)); |
| /* verify created revs */ |
| path_revs[0].rev = 2; /* (root) */ |
| path_revs[1].rev = 2; /* iota */ |
| SVN_ERR (verify_path_revs (rev_root, path_revs, 20, subpool)); |
| |
| /*** clear the per-commit pool */ |
| svn_pool_clear (subpool); |
| /* begin a new transaction */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| /* make mods */ |
| SVN_ERR (svn_test__set_file_contents |
| (txn_root, "A/D/H/omega", "pointless mod here", subpool)); |
| /* verify created revs */ |
| path_revs[0].rev = SVN_INVALID_REVNUM; /* (root) */ |
| path_revs[2].rev = SVN_INVALID_REVNUM; /* A */ |
| path_revs[11].rev = SVN_INVALID_REVNUM; /* D */ |
| path_revs[17].rev = SVN_INVALID_REVNUM; /* H */ |
| path_revs[20].rev = SVN_INVALID_REVNUM; /* omega */ |
| SVN_ERR (verify_path_revs (txn_root, path_revs, 20, subpool)); |
| /* commit transaction */ |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| /* get a revision root for the new revision */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, subpool)); |
| /* verify created revs */ |
| path_revs[0].rev = 3; /* (root) */ |
| path_revs[2].rev = 3; /* A */ |
| path_revs[11].rev = 3; /* D */ |
| path_revs[17].rev = 3; /* H */ |
| path_revs[20].rev = 3; /* omega */ |
| SVN_ERR (verify_path_revs (rev_root, path_revs, 20, subpool)); |
| |
| |
| /* Destroy the per-commit subpool. */ |
| svn_pool_destroy (subpool); |
| |
| /* Close the filesystem. */ |
| svn_fs_close_fs (fs); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| check_related (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create (pool); |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t youngest_rev = 0; |
| |
| *msg = "test svn_fs_check_related"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-check-related", pool)); |
| |
| /*** Step I: Build up some state in our repository through a series |
| of commits */ |
| |
| /* Using files because bubble-up complicates the testing. However, |
| the algorithm itself is ambivalent about what type of node is |
| being examined. |
| |
| - New files show up in this order (through time): A,B,C,D,E,F |
| - Number following filename is the revision. |
| - Vertical motion shows revision history |
| - Horizontal motion show copy history. |
| |
| A1---------C4 E7 |
| | | | |
| A2 C5 E8---F9 |
| | | | |
| A3---B4 C6 F10 |
| | | |
| A4 B5----------D6 |
| | | |
| B6 D7 |
| */ |
| /* Revision 1 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "A", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A", "1", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 2 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A", "2", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 3 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A", "3", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 4 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A", "4", subpool)); |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, 3, subpool)); |
| SVN_ERR (svn_fs_copy (rev_root, "A", txn_root, "B", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "B", "4", subpool)); |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, 1, subpool)); |
| SVN_ERR (svn_fs_copy (rev_root, "A", txn_root, "C", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "C", "4", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 5 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "B", "5", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "C", "5", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 6 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "B", "6", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "C", "6", subpool)); |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, 5, subpool)); |
| SVN_ERR (svn_fs_copy (rev_root, "B", txn_root, "D", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "D", "5", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 7 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "D", "7", subpool)); |
| SVN_ERR (svn_fs_make_file (txn_root, "E", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "E", "7", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 8 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "E", "8", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 9 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, 8, subpool)); |
| SVN_ERR (svn_fs_copy (rev_root, "E", txn_root, "F", subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "F", "9", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (subpool); |
| /* Revision 10 */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "F", "10", subpool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| |
| /*** Step II: Exhausively verify relationship between all nodes in |
| existence. */ |
| { |
| int i, j; |
| |
| struct path_rev_t |
| { |
| const char *path; |
| const svn_revnum_t rev; |
| }; |
| |
| /* Our 16 existing files/revisions. */ |
| struct path_rev_t path_revs[16] = { |
| { "A", 1 }, { "A", 2 }, { "A", 3 }, { "A", 4 }, |
| { "B", 4 }, { "B", 5 }, { "B", 6 }, { "C", 4 }, |
| { "C", 5 }, { "C", 6 }, { "D", 6 }, { "D", 7 }, |
| { "E", 7 }, { "E", 8 }, { "F", 9 }, { "F", 10 } |
| }; |
| |
| int related_matrix[16][16] = { |
| /* A1 ... F10 across the top here*/ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A1 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A2 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A3 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A4 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* B4 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* B5 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* B6 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* C4 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* C5 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* C6 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* D6 */ |
| { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* D7 */ |
| { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, /* E7 */ |
| { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, /* E8 */ |
| { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, /* F9 */ |
| { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 } /* F10 */ |
| }; |
| |
| /* Here's the fun part. Running the tests. */ |
| for (i = 0; i < 16; i++) |
| { |
| for (j = 0; j < 16; j++) |
| { |
| struct path_rev_t pr1 = path_revs[i]; |
| struct path_rev_t pr2 = path_revs[j]; |
| svn_fs_id_t *id1, *id2; |
| int related = 0; |
| |
| /* Get the ID for the first path/revision combination. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, pr1.rev, pool)); |
| SVN_ERR (svn_fs_node_id (&id1, rev_root, pr1.path, pool)); |
| |
| /* Get the ID for the second path/revision combination. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, pr2.rev, pool)); |
| SVN_ERR (svn_fs_node_id (&id2, rev_root, pr2.path, pool)); |
| |
| /* <exciting> Now, run the relationship check! </exciting> */ |
| SVN_ERR (svn_fs_check_related (&related, fs, id1, id2, pool)); |
| if (related == related_matrix[i][j]) |
| { |
| /* xlnt! */ |
| } |
| else if (related && (! related_matrix[i][j])) |
| { |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, 0, NULL, pool, |
| "expected `%s:%d' to be related to `%s:%d'; it was not", |
| pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev); |
| } |
| else if ((! related) && related_matrix[i][j]) |
| { |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, 0, NULL, pool, |
| "expected `%s:%d' to not be related to `%s:%d'; it was", |
| pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev); |
| } |
| |
| svn_pool_clear (subpool); |
| } /* for ... */ |
| } /* for ... */ |
| } |
| |
| /* Destroy the subpool. */ |
| svn_pool_destroy (subpool); |
| |
| /* Close the filesystem. */ |
| svn_fs_close_fs (fs); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static const char * |
| print_chrevs (const apr_array_header_t *revs_got, |
| const svn_revnum_t num_revs_expected, |
| const svn_revnum_t *revs_expected, |
| apr_pool_t *pool) |
| { |
| int i; |
| const char *outstr; |
| svn_revnum_t rev; |
| |
| outstr = apr_psprintf (pool, "Got: { "); |
| if (revs_got) |
| { |
| for (i = 0; i < revs_got->nelts; i++) |
| { |
| rev = ((svn_revnum_t *)revs_got->elts)[i]; |
| outstr = apr_pstrcat (pool, |
| outstr, |
| apr_psprintf (pool, "%ld ", (long int)rev), |
| NULL); |
| } |
| } |
| outstr = apr_pstrcat (pool, outstr, "} Expected: { ", NULL); |
| for (i = 0; i < num_revs_expected; i++) |
| { |
| outstr = apr_pstrcat (pool, |
| outstr, |
| apr_psprintf (pool, "%ld ", |
| (long int)revs_expected[i]), |
| NULL); |
| } |
| return apr_pstrcat (pool, outstr, "}", NULL); |
| } |
| |
| static svn_error_t * |
| revisions_changed (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *spool = svn_pool_create (pool); |
| svn_fs_t *fs; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| svn_revnum_t youngest_rev = 0; |
| |
| *msg = "test svn_fs_revisions_changed"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a filesystem and repository. */ |
| SVN_ERR (svn_test__create_fs (&fs, "test-repo-revisions-changed", pool)); |
| |
| /*** Testing Algorithm *** |
| |
| 1. Create a greek tree in revision 1. |
| 2. Make a series of new revisions, changing a file here and file |
| there. |
| 3. Loop over each path in each revision, verifying that we get |
| the right revisions-changed array back from the filesystem. |
| */ |
| |
| /* Created the greek tree in revision 1. */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); |
| SVN_ERR (svn_test__create_greek_tree (txn_root, spool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (spool); |
| |
| /* Revision 2 - mu, alpha, omega */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/mu", "2", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/E/alpha", "2", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/omega", "2", spool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (spool); |
| |
| /* Revision 3 - iota, lambda, psi, omega */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "iota", "3", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/lambda", "3", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/psi", "3", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/omega", "3", spool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (spool); |
| |
| /* Revision 4 - iota, beta, gamma, pi, rho */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "iota", "4", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/E/beta", "4", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/gamma", "4", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/pi", "4", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/rho", "4", spool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (spool); |
| |
| /* Revision 5 - mu, alpha, tau, chi */ |
| SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); |
| SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/mu", "5", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/E/alpha", "5", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/tau", "5", spool)); |
| SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/chi", "5", spool)); |
| SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); |
| SVN_ERR (svn_fs_close_txn (txn)); |
| svn_pool_clear (spool); |
| |
| /* Now, it's time to verify our results. */ |
| { |
| int j; |
| const char *greek_paths[21] = { |
| /* 0 */ "", |
| /* 1 */ "iota", |
| /* 2 */ "A", |
| /* 3 */ "A/mu", |
| /* 4 */ "A/B", |
| /* 5 */ "A/B/lambda", |
| /* 6 */ "A/B/E", |
| /* 7 */ "A/B/E/alpha", |
| /* 8 */ "A/B/E/beta", |
| /* 9 */ "A/B/F", |
| /* 10 */ "A/C", |
| /* 11 */ "A/D", |
| /* 12 */ "A/D/gamma", |
| /* 13 */ "A/D/G", |
| /* 14 */ "A/D/G/pi", |
| /* 15 */ "A/D/G/rho", |
| /* 16 */ "A/D/G/tau", |
| /* 17 */ "A/D/H", |
| /* 18 */ "A/D/H/chi", |
| /* 19 */ "A/D/H/psi", |
| /* 20 */ "A/D/H/omega", |
| }; |
| |
| /* Number of, and list of, changed revisions for each path. Note |
| that for now, bubble-up in directories causes the directory to |
| appear changed though no entries were added or removed, and no |
| property mods occured. */ |
| static const svn_revnum_t chrevs[21][7] = { |
| /* path, num, revisions changed... */ |
| /* 0 */ { 6, 5, 4, 3, 2, 1, 0}, |
| /* 1 */ { 3, 4, 3, 1 }, |
| /* 2 */ { 5, 5, 4, 3, 2, 1 }, |
| /* 3 */ { 3, 5, 2, 1 }, |
| /* 4 */ { 5, 5, 4, 3, 2, 1 }, |
| /* 5 */ { 2, 3, 1 }, |
| /* 6 */ { 4, 5, 4, 2, 1 }, |
| /* 7 */ { 3, 5, 2, 1 }, |
| /* 8 */ { 2, 4, 1 }, |
| /* 9 */ { 1, 1 }, |
| /* 10 */ { 1, 1 }, |
| /* 11 */ { 5, 5, 4, 3, 2, 1 }, |
| /* 12 */ { 2, 4, 1 }, |
| /* 13 */ { 3, 5, 4, 1 }, |
| /* 14 */ { 2, 4, 1 }, |
| /* 15 */ { 2, 4, 1 }, |
| /* 16 */ { 2, 5, 1 }, |
| /* 17 */ { 4, 5, 3, 2, 1 }, |
| /* 18 */ { 2, 5, 1 }, |
| /* 19 */ { 2, 3, 1 }, |
| /* 20 */ { 3, 3, 2, 1 } |
| }; |
| |
| apr_array_header_t *revs; |
| |
| /* Get a revision root. */ |
| SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, pool)); |
| |
| /* Now, for each path in the revision root, get its |
| changed-revisions array and compare the array to the static |
| results above. */ |
| |
| /* ### todo: For now, we aren't checking the root node. That's |
| because there is an inconsistency in the filesystem ID code. |
| IDs are expected to never end in ".0", yet the first node ever |
| created in each filesystem is always "0.0"!! */ |
| for (j = 1; j < 21; j++) |
| { |
| int i; |
| const char *path = greek_paths[j]; |
| const svn_revnum_t num_revs = chrevs[j][0]; |
| apr_array_header_t *paths; |
| |
| paths = apr_array_make (spool, 1, sizeof (const char *)); |
| (*(const char **)apr_array_push(paths)) = path; |
| |
| SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, paths, spool)); |
| |
| /* Are we at least looking at the right number of returned |
| revisions? */ |
| if ((! revs) || (revs->nelts != num_revs)) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, spool, |
| "Changed revisions differ from expected for `%s'\n%s", |
| path, print_chrevs (revs, num_revs, &(chrevs[j][1]), spool)); |
| |
| /* Do the revisions lists match up exactly? */ |
| for (i = 0; i < num_revs; i++) |
| { |
| svn_revnum_t rev = ((svn_revnum_t *)revs->elts)[i]; |
| if (rev != chrevs[j][i + 1]) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, 0, NULL, spool, |
| "Changed revisions differ from expected for `%s'\n%s", |
| path, print_chrevs (revs, num_revs, &(chrevs[j][1]), spool)); |
| } |
| |
| /* Clear the per-iteration subpool. */ |
| svn_pool_clear (spool); |
| } |
| } |
| |
| /* Destroy the subpool. */ |
| svn_pool_destroy (spool); |
| |
| /* Close the filesystem. */ |
| svn_fs_close_fs (fs); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| |
| /* The test table. */ |
| |
| svn_error_t * (*test_funcs[]) (const char **msg, |
| svn_boolean_t msg_only, |
| apr_pool_t *pool) = { |
| 0, |
| create_berkeley_filesystem, |
| open_berkeley_filesystem, |
| trivial_transaction, |
| reopen_trivial_transaction, |
| create_file_transaction, |
| verify_txn_list, |
| call_functions_with_unopened_fs, |
| write_and_read_file, |
| create_mini_tree_transaction, |
| create_greek_tree_transaction, |
| list_directory, |
| revision_props, |
| transaction_props, |
| node_props, |
| delete_mutables, |
| delete, |
| abort_txn, |
| test_tree_node_validation, |
| fetch_by_id, |
| fetch_youngest_rev, |
| basic_commit, |
| copy_test, |
| link_test, |
| merging_commit, |
| merge_re_id, |
| commit_date, |
| check_old_revisions, |
| check_all_revisions, |
| medium_file_integrity, |
| large_file_integrity, |
| check_root_revision, |
| undeltify_deltify, |
| test_node_created_rev, |
| check_related, |
| revisions_changed, |
| 0 |
| }; |
| |
| |
| /* |
| * local variables: |
| * eval: (load-file "../../../tools/dev/svn-dev.el") |
| * end: |
| */ |