svn_error_t * svn_repos_fs_commit_txn(const char **conflict_p, svn_repos_t *repos, svn_revnum_t *new_rev, svn_fs_txn_t *txn, apr_pool_t *pool) { svn_error_t *err; const char *txn_name; /* Run pre-commit hooks. */ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool)); /* Commit. */ SVN_ERR(svn_fs_commit_txn(conflict_p, new_rev, txn, pool)); /* Run post-commit hooks. Notice that we're wrapping the error with a -specific- errorcode, so that our caller knows not to try and abort the transaction. */ if ((err = svn_repos__hooks_post_commit(repos, *new_rev, pool))) return svn_error_create (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, _("Commit succeeded, but post-commit hook failed")); return SVN_NO_ERROR; }
svn_error_t * svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p, svn_repos_t *repos, svn_revnum_t rev, apr_hash_t *revprop_table, apr_pool_t *pool) { apr_array_header_t *revprops; const char *txn_name; svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR); apr_hash_t *hooks_env; /* Parse the hooks-env file (if any). */ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, pool, pool)); /* Begin the transaction, ask for the fs to do on-the-fly lock checks. We fetch its name, too, so the start-commit hook can use it. */ SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev, SVN_FS_TXN_CHECK_LOCKS, pool)); SVN_ERR(svn_fs_txn_name(&txn_name, *txn_p, pool)); /* We pass the revision properties to the filesystem by adding them as properties on the txn. Later, when we commit the txn, these properties will be copied into the newly created revision. */ revprops = svn_prop_hash_to_array(revprop_table, pool); SVN_ERR(svn_repos_fs_change_txn_props(*txn_p, revprops, pool)); /* Run start-commit hooks. */ SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env, author ? author->data : NULL, repos->client_capabilities, txn_name, pool)); return SVN_NO_ERROR; }
svn_error_t * svn_repos_fs_commit_txn(const char **conflict_p, svn_repos_t *repos, svn_revnum_t *new_rev, svn_fs_txn_t *txn, apr_pool_t *pool) { svn_error_t *err, *err2; const char *txn_name; *new_rev = SVN_INVALID_REVNUM; /* Run pre-commit hooks. */ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool)); /* Commit. */ err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool); if (! SVN_IS_VALID_REVNUM(*new_rev)) return err; /* Run post-commit hooks. */ if ((err2 = svn_repos__hooks_post_commit(repos, *new_rev, pool))) { err2 = svn_error_create (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2, _("Commit succeeded, but post-commit hook failed")); } return svn_error_compose_create(err, err2); }
svn_error_t * svn_repos_fs_commit_txn(const char **conflict_p, svn_repos_t *repos, svn_revnum_t *new_rev, svn_fs_txn_t *txn, apr_pool_t *pool) { svn_error_t *err, *err2; const char *txn_name; apr_hash_t *props; apr_pool_t *iterpool; apr_hash_index_t *hi; apr_hash_t *hooks_env; *new_rev = SVN_INVALID_REVNUM; /* Parse the hooks-env file (if any). */ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, pool, pool)); /* Run pre-commit hooks. */ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool)); /* Remove any ephemeral transaction properties. */ SVN_ERR(svn_fs_txn_proplist(&props, txn, pool)); iterpool = svn_pool_create(pool); for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { const void *key; apr_hash_this(hi, &key, NULL, NULL); svn_pool_clear(iterpool); if (strncmp(key, SVN_PROP_TXN_PREFIX, (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0) { SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool)); } } svn_pool_destroy(iterpool); /* Commit. */ err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool); if (! SVN_IS_VALID_REVNUM(*new_rev)) return err; /* Run post-commit hooks. */ if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env, *new_rev, txn_name, pool))) { err2 = svn_error_create (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2, _("Commit succeeded, but post-commit hook failed")); } return svn_error_compose_create(err, err2); }
static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton) { struct dir_baton *dirb; struct edit_baton *eb = edit_baton; svn_revnum_t youngest; /* Ignore BASE_REVISION. We always build our transaction against HEAD. However, we will keep it in our dir baton for out of dateness checks. */ SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool)); if (base_revision > youngest) return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, _("No such revision %ld (HEAD is %ld)"), base_revision, youngest); /* Unless we've been instructed to use a specific transaction, we'll make our own. */ if (eb->txn_owner) { SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn), eb->repos, youngest, eb->revprop_table, eb->pool)); } else /* Even if we aren't the owner of the transaction, we might have been instructed to set some properties. */ { apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table, pool); SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool)); } SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool)); SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool)); /* Create a root dir baton. The `base_path' field is an -absolute- path in the filesystem, upon which all further editor paths are based. */ dirb = apr_pcalloc(pool, sizeof(*dirb)); dirb->edit_baton = edit_baton; dirb->parent = NULL; dirb->pool = pool; dirb->was_copied = FALSE; dirb->path = apr_pstrdup(pool, eb->base_path); dirb->base_rev = base_revision; *root_baton = dirb; return SVN_NO_ERROR; }
dav_error * dav_svn__create_txn(dav_svn_repos *repos, const char **ptxn_name, apr_hash_t *revprops, apr_pool_t *pool) { svn_revnum_t rev; svn_fs_txn_t *txn; svn_error_t *serr; if (! revprops) { revprops = apr_hash_make(pool); } if (repos->username) { svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR, svn_string_create(repos->username, pool)); } serr = dav_svn__get_youngest_rev(&rev, repos, pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not determine youngest revision", repos->pool); } serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev, revprops, repos->pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not begin a transaction", repos->pool); } serr = svn_fs_txn_name(ptxn_name, txn, pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not fetch transaction name", repos->pool); } return NULL; }
svn_error_t * svn_fs__editor_create(svn_editor_t **editor, const char **txn_name, svn_fs_t *fs, apr_uint32_t flags, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_revnum_t revision; svn_fs_txn_t *txn; SVN_ERR(svn_fs_youngest_rev(&revision, fs, scratch_pool)); SVN_ERR(svn_fs_begin_txn2(&txn, fs, revision, flags, result_pool)); SVN_ERR(svn_fs_txn_name(txn_name, txn, result_pool)); return svn_error_trace(make_editor(editor, txn, cancel_func, cancel_baton, result_pool, scratch_pool)); }
dav_error * dav_svn__create_activity(const dav_svn_repos *repos, const char **ptxn_name, apr_pool_t *pool) { svn_revnum_t rev; svn_fs_txn_t *txn; svn_error_t *serr; serr = svn_fs_youngest_rev(&rev, repos->fs, pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not determine youngest revision", repos->pool); } serr = svn_repos_fs_begin_txn_for_commit(&txn, repos->repos, rev, repos->username, NULL, repos->pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not begin a transaction", repos->pool); } serr = svn_fs_txn_name(ptxn_name, txn, pool); if (serr != NULL) { return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not fetch transaction name", repos->pool); } return NULL; }
static svn_error_t * close_revision(void *baton) { struct revision_baton *rb = baton; struct parse_baton *pb = rb->pb; const char *conflict_msg = NULL; svn_revnum_t committed_rev; svn_error_t *err; const char *txn_name = NULL; apr_hash_t *hooks_env; /* If we're skipping this revision we're done here. */ if (rb->skipped) return SVN_NO_ERROR; if (rb->rev == 0) { /* Special case: set revision 0 properties when loading into an 'empty' filesystem. */ svn_revnum_t youngest_rev; SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool)); if (youngest_rev == 0) { apr_hash_t *orig_props; apr_hash_t *new_props; apr_array_header_t *diff; int i; SVN_ERR(svn_fs_revision_proplist(&orig_props, pb->fs, 0, rb->pool)); new_props = svn_prop_array_to_hash(rb->revprops, rb->pool); SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool)); for (i = 0; i < diff->nelts; i++) { const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t); SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value, pb->validate_props, rb->pool)); } } return SVN_NO_ERROR; } /* If the dumpstream doesn't have an 'svn:date' property and we aren't ignoring the dates in the dumpstream altogether, remove any 'svn:date' revision property that was set by FS layer when the TXN was created. */ if (! (pb->ignore_dates || rb->datestamp)) { svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t); prop->name = SVN_PROP_REVISION_DATE; prop->value = NULL; } /* Apply revision property changes. */ if (rb->pb->validate_props) SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool)); else SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool)); /* Get the txn name and hooks environment if they will be needed. */ if (pb->use_pre_commit_hook || pb->use_post_commit_hook) { SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path, rb->pool, rb->pool)); err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool); if (err) { svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); return svn_error_trace(err); } } /* Run the pre-commit hook, if so commanded. */ if (pb->use_pre_commit_hook) { err = svn_repos__hooks_pre_commit(pb->repos, hooks_env, txn_name, rb->pool); if (err) { svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); return svn_error_trace(err); } } /* Commit. */ err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool); if (SVN_IS_VALID_REVNUM(committed_rev)) { if (err) { /* ### Log any error, but better yet is to rev ### close_revision()'s API to allow both committed_rev and err ### to be returned, see #3768. */ svn_error_clear(err); } } else { svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); if (conflict_msg) return svn_error_quick_wrap(err, conflict_msg); else return svn_error_trace(err); } /* Run post-commit hook, if so commanded. */ if (pb->use_post_commit_hook) { if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env, committed_rev, txn_name, rb->pool))) return svn_error_create (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, _("Commit succeeded, but post-commit hook failed")); } /* After a successful commit, must record the dump-rev -> in-repos-rev mapping, so that copyfrom instructions in the dump file can look up the correct repository revision to copy from. */ set_revision_mapping(pb->rev_map, rb->rev, committed_rev); /* If the incoming dump stream has non-contiguous revisions (e.g. from using svndumpfilter --drop-empty-revs without --renumber-revs) then we must account for the missing gaps in PB->REV_MAP. Otherwise we might not be able to map all mergeinfo source revisions to the correct revisions in the target repos. */ if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) && (rb->rev != pb->last_rev_mapped + 1)) { svn_revnum_t i; for (i = pb->last_rev_mapped + 1; i < rb->rev; i++) { set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); } } /* Update our "last revision mapped". */ pb->last_rev_mapped = rb->rev; /* Deltify the predecessors of paths changed in this revision. */ SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->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_txn_committed, pb->notify_pool); notify->new_revision = committed_rev; notify->old_revision = ((committed_rev == rb->rev) ? SVN_INVALID_REVNUM : rb->rev); pb->notify_func(pb->notify_baton, notify, pb->notify_pool); svn_pool_clear(pb->notify_pool); } return SVN_NO_ERROR; }
static svn_error_t * close_revision(void *baton) { struct revision_baton *rb = baton; struct parse_baton *pb = rb->pb; const char *conflict_msg = NULL; svn_revnum_t committed_rev; svn_error_t *err; const char *txn_name = NULL; apr_hash_t *hooks_env; /* If we're skipping this revision or it has an invalid revision number, we're done here. */ if (rb->skipped || (rb->rev <= 0)) return SVN_NO_ERROR; /* Get the txn name and hooks environment if they will be needed. */ if (pb->use_pre_commit_hook || pb->use_post_commit_hook) { SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path, rb->pool, rb->pool)); err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool); if (err) { svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); return svn_error_trace(err); } } /* Run the pre-commit hook, if so commanded. */ if (pb->use_pre_commit_hook) { err = svn_repos__hooks_pre_commit(pb->repos, hooks_env, txn_name, rb->pool); if (err) { svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); return svn_error_trace(err); } } /* Commit. */ err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool); if (SVN_IS_VALID_REVNUM(committed_rev)) { if (err) { /* ### Log any error, but better yet is to rev ### close_revision()'s API to allow both committed_rev and err ### to be returned, see #3768. */ svn_error_clear(err); } } else { svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); if (conflict_msg) return svn_error_quick_wrap(err, conflict_msg); else return svn_error_trace(err); } /* Run post-commit hook, if so commanded. */ if (pb->use_post_commit_hook) { if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env, committed_rev, txn_name, rb->pool))) return svn_error_create (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, _("Commit succeeded, but post-commit hook failed")); } /* After a successful commit, must record the dump-rev -> in-repos-rev mapping, so that copyfrom instructions in the dump file can look up the correct repository revision to copy from. */ set_revision_mapping(pb->rev_map, rb->rev, committed_rev); /* If the incoming dump stream has non-contiguous revisions (e.g. from using svndumpfilter --drop-empty-revs without --renumber-revs) then we must account for the missing gaps in PB->REV_MAP. Otherwise we might not be able to map all mergeinfo source revisions to the correct revisions in the target repos. */ if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) && (rb->rev != pb->last_rev_mapped + 1)) { svn_revnum_t i; for (i = pb->last_rev_mapped + 1; i < rb->rev; i++) { set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); } } /* Update our "last revision mapped". */ pb->last_rev_mapped = rb->rev; /* Deltify the predecessors of paths changed in this revision. */ SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool)); /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the current clock-time. We don't want that, we want to preserve history exactly. Good thing revision props aren't versioned! Note that if rb->datestamp is NULL, that's fine -- if the dump data doesn't carry a datestamp, we want to preserve that fact in the load. */ SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE, rb->datestamp, pb->validate_props, rb->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_txn_committed, pb->notify_pool); notify->new_revision = committed_rev; notify->old_revision = ((committed_rev == rb->rev) ? SVN_INVALID_REVNUM : rb->rev); pb->notify_func(pb->notify_baton, notify, pb->notify_pool); svn_pool_clear(pb->notify_pool); } 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; }