/* This implements svn_editor_cb_add_directory_t */ static svn_error_t * add_directory_cb(void *baton, const char *relpath, const apr_array_header_t *children, apr_hash_t *props, svn_revnum_t replaces_rev, apr_pool_t *scratch_pool) { struct edit_baton *eb = baton; const char *fspath = FSPATH(relpath, scratch_pool); svn_fs_root_t *root; /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about, so we don't need to be aware of what children will be created. */ SVN_ERR(get_root(&root, eb)); if (SVN_IS_VALID_REVNUM(replaces_rev)) { SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool)); SVN_ERR(svn_fs_delete(root, fspath, scratch_pool)); } else { SVN_ERR(can_create(root, fspath, scratch_pool)); } SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool)); SVN_ERR(add_new_props(root, fspath, props, scratch_pool)); return SVN_NO_ERROR; }
static svn_error_t * test_add_directory(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_revision, apr_pool_t *dir_pool, void **child_baton) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; struct dir_baton *db = apr_pcalloc(dir_pool, sizeof(*db)); /* Construct the full path of the new directory */ db->full_path = svn_path_join(eb->root_path, path, eb->pool); db->edit_baton = eb; if (copyfrom_path) /* add with history */ { svn_fs_root_t *rev_root = NULL; SVN_ERR(svn_fs_revision_root(&rev_root, eb->fs, copyfrom_revision, dir_pool)); SVN_ERR(svn_fs_copy(rev_root, copyfrom_path, eb->txn_root, db->full_path, dir_pool)); } else /* add without history */ SVN_ERR(svn_fs_make_dir(eb->txn_root, db->full_path, dir_pool)); *child_baton = db; return SVN_NO_ERROR; }
/* Perform a copy or a plain add. * * For a copy, also adjust the copy-from rev, check any copy-source checksum, * and send a notification. */ static svn_error_t * maybe_add_with_history(struct node_baton *nb, struct revision_baton *rb, apr_pool_t *pool) { struct parse_baton *pb = rb->pb; if ((nb->copyfrom_path == NULL) || (! pb->use_history)) { /* Add empty file or dir, without history. */ if (nb->kind == svn_node_file) SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool)); else if (nb->kind == svn_node_dir) SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool)); } else { /* Hunt down the source revision in this fs. */ svn_fs_root_t *copy_root; svn_revnum_t copyfrom_rev; /* Try to find the copyfrom revision in the revision map; failing that, fall back to the revision offset approach. */ copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev); if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) copyfrom_rev = nb->copyfrom_rev - rb->rev_offset; if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, _("Relative source revision %ld is not" " available in current repository"), copyfrom_rev); SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool)); if (nb->copy_source_checksum) { svn_checksum_t *checksum; SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root, nb->copyfrom_path, TRUE, pool)); if (!svn_checksum_match(nb->copy_source_checksum, checksum)) return svn_checksum_mismatch_err(nb->copy_source_checksum, checksum, pool, _("Copy source checksum mismatch on copy from '%s'@%ld\n" "to '%s' in rev based on r%ld"), nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev); } SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path, rb->txn_root, nb->path, pool)); if (pb->notify_func) { /* ### TODO: Use proper scratch pool instead of pb->notify_pool */ svn_repos_notify_t *notify = svn_repos_notify_create( svn_repos_notify_load_copied_node, pb->notify_pool); pb->notify_func(pb->notify_baton, notify, pb->notify_pool); svn_pool_clear(pb->notify_pool); } } return SVN_NO_ERROR; }
/* This function is the shared guts of add_file() and add_directory(), which see for the meanings of the parameters. The only extra parameter here is IS_DIR, which is TRUE when adding a directory, and FALSE when adding a file. */ static svn_error_t * add_file_or_directory(const char *path, void *parent_baton, const char *copy_path, svn_revnum_t copy_revision, svn_boolean_t is_dir, apr_pool_t *pool, void **return_baton) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; apr_pool_t *subpool = svn_pool_create(pool); svn_boolean_t was_copied = FALSE; const char *full_path; full_path = svn_fspath__join(eb->base_path, svn_relpath_canonicalize(path, pool), pool); /* Sanity check. */ if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision))) return svn_error_createf (SVN_ERR_FS_GENERAL, NULL, _("Got source path but no source revision for '%s'"), full_path); if (copy_path) { const char *fs_path; svn_fs_root_t *copy_root; svn_node_kind_t kind; size_t repos_url_len; svn_repos_authz_access_t required; /* Copy requires recursive write access to the destination path and write access to the parent path. */ required = svn_authz_write | (is_dir ? svn_authz_recursive : 0); SVN_ERR(check_authz(eb, full_path, eb->txn_root, required, subpool)); SVN_ERR(check_authz(eb, pb->path, eb->txn_root, svn_authz_write, subpool)); /* Check PATH in our transaction. Make sure it does not exist unless its parent directory was copied (in which case, the thing might have been copied in as well), else return an out-of-dateness error. */ SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool)); if ((kind != svn_node_none) && (! pb->was_copied)) return out_of_date(full_path, kind); /* For now, require that the url come from the same repository that this commit is operating on. */ copy_path = svn_path_uri_decode(copy_path, subpool); repos_url_len = strlen(eb->repos_url); if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0) return svn_error_createf (SVN_ERR_FS_GENERAL, NULL, _("Source url '%s' is from different repository"), copy_path); fs_path = apr_pstrdup(subpool, copy_path + repos_url_len); /* Now use the "fs_path" as an absolute path within the repository to make the copy from. */ SVN_ERR(svn_fs_revision_root(©_root, eb->fs, copy_revision, subpool)); /* Copy also requires (recursive) read access to the source */ required = svn_authz_read | (is_dir ? svn_authz_recursive : 0); SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool)); SVN_ERR(svn_fs_copy(copy_root, fs_path, eb->txn_root, full_path, subpool)); was_copied = TRUE; } else { /* No ancestry given, just make a new directory or empty file. Note that we don't perform an existence check here like the copy-from case does -- that's because svn_fs_make_*() already errors out if the file already exists. Verify write access to the full path and to the parent. */ SVN_ERR(check_authz(eb, full_path, eb->txn_root, svn_authz_write, subpool)); SVN_ERR(check_authz(eb, pb->path, eb->txn_root, svn_authz_write, subpool)); if (is_dir) SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool)); else SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool)); } /* Cleanup our temporary subpool. */ svn_pool_destroy(subpool); /* Build a new child baton. */ if (is_dir) { *return_baton = make_dir_baton(eb, pb, full_path, was_copied, SVN_INVALID_REVNUM, pool); } else { struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); new_fb->edit_baton = eb; new_fb->path = full_path; *return_baton = new_fb; } return SVN_NO_ERROR; }
static svn_error_t * changes_fetch_ordering(const char **msg, svn_boolean_t msg_only, svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_revnum_t youngest_rev = 0; const char *txn_name; svn_fs_txn_t *txn; svn_fs_root_t *txn_root, *rev_root; struct changes_args args; apr_pool_t *subpool = svn_pool_create(pool); apr_hash_index_t *hi; *msg = "verify ordered-ness of fetched compressed changes"; if (msg_only) return SVN_NO_ERROR; /* Create a new fs and repos */ SVN_ERR(svn_test__create_fs (&fs, "test-repo-changes-fetch-ordering", "bdb", pool)); /*** REVISION 1: Make some files and dirs. ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); { static svn_test__txn_script_command_t script_entries[] = { { 'a', "dir1", 0 }, { 'a', "file1", "This is the file 'file1'.\n" }, { 'a', "dir1/file2", "This is the file 'file2'.\n" }, { 'a', "dir1/file3", "This is the file 'file3'.\n" }, { 'a', "dir1/file4", "This is the file 'file4'.\n" }, }; SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool)); } SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); svn_pool_clear(subpool); /*** REVISION 2: Delete and add some stuff, non-depth-first. ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); /* Don't use subpool, txn_name is used after subpool is cleared */ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); { static svn_test__txn_script_command_t script_entries[] = { { 'd', "file1", "This is the file 'file1'.\n" }, { 'd', "dir1/file2", "This is the file 'file2'.\n" }, { 'd', "dir1/file3", "This is the file 'file3'.\n" }, { 'a', "dir1/file5", "This is the file 'file4'.\n" }, { 'a', "dir1/dir2", 0 }, { 'd', "dir1", 0 }, { 'a', "dir3", 0 }, }; SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 7, subpool)); } SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); svn_pool_clear(subpool); /*** TEST: We should have only three changes, the deletion of 'file1' the deletion of 'dir1', and the addition of 'dir3'. ***/ args.fs = fs; args.key = txn_name; SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args, subpool)); if ((! args.changes) || (apr_hash_count(args.changes) != 3)) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "expected changes"); for (hi = apr_hash_first(subpool, args.changes); hi; hi = apr_hash_next(hi)) { const void *key; void *val; svn_fs_path_change_t *change; /* KEY will be the path, VAL the change. */ apr_hash_this(hi, &key, NULL, &val); change = val; if ((change->change_kind == svn_fs_path_change_add) && (strcmp(key, "/dir3") == 0)) ; else if ((change->change_kind == svn_fs_path_change_delete) && ((strcmp(key, "/dir1") == 0) || (strcmp(key, "/file1") == 0))) ; else return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "got wrong changes"); } /*** REVISION 3: Do the same stuff as in revision 1. ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); { static svn_test__txn_script_command_t script_entries[] = { { 'a', "dir1", 0 }, { 'a', "file1", "This is the file 'file1'.\n" }, { 'a', "dir1/file2", "This is the file 'file2'.\n" }, { 'a', "dir1/file3", "This is the file 'file3'.\n" }, { 'a', "dir1/file4", "This is the file 'file4'.\n" }, }; SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool)); } SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); svn_pool_clear(subpool); /*** REVISION 4: Do the same stuff as in revision 2, but use a copy overwrite of the top directory (instead of a delete) to test that the 'replace' change type works, too. (And add 'dir4' instead of 'dir3', since 'dir3' still exists). ***/ SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); /* Don't use subpool, txn_name is used after subpool is cleared */ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, subpool)); { static svn_test__txn_script_command_t script_entries[] = { { 'd', "file1", "This is the file 'file1'.\n" }, { 'd', "dir1/file2", "This is the file 'file2'.\n" }, { 'd', "dir1/file3", "This is the file 'file3'.\n" }, { 'a', "dir1/file5", "This is the file 'file4'.\n" }, { 'a', "dir1/dir2", 0 }, }; SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool)); SVN_ERR(svn_fs_copy(rev_root, "dir1", txn_root, "dir1", subpool)); SVN_ERR(svn_fs_make_dir(txn_root, "dir4", subpool)); } SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); svn_pool_clear(subpool); /*** TEST: We should have only three changes, the deletion of 'file1' the replacement of 'dir1', and the addition of 'dir4'. ***/ args.fs = fs; args.key = txn_name; SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args, subpool)); if ((! args.changes) || (apr_hash_count(args.changes) != 3)) return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "expected changes"); for (hi = apr_hash_first(subpool, args.changes); hi; hi = apr_hash_next(hi)) { const void *key; void *val; svn_fs_path_change_t *change; /* KEY will be the path, VAL the change. */ apr_hash_this(hi, &key, NULL, &val); change = val; if ((change->change_kind == svn_fs_path_change_add) && (strcmp(key, "/dir4") == 0)) ; else if ((change->change_kind == svn_fs_path_change_replace) && (strcmp(key, "/dir1") == 0)) ; else if ((change->change_kind == svn_fs_path_change_delete) && (strcmp(key, "/file1") == 0)) ; else return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "got wrong changes"); } return SVN_NO_ERROR; }