summaryrefslogtreecommitdiff
path: root/subversion/tests/libsvn_client
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/tests/libsvn_client')
-rw-r--r--subversion/tests/libsvn_client/client-test.c684
-rw-r--r--subversion/tests/libsvn_client/mtcc-test.c817
2 files changed, 1485 insertions, 16 deletions
diff --git a/subversion/tests/libsvn_client/client-test.c b/subversion/tests/libsvn_client/client-test.c
index 9fad3bb..4e2a6d8 100644
--- a/subversion/tests/libsvn_client/client-test.c
+++ b/subversion/tests/libsvn_client/client-test.c
@@ -31,9 +31,12 @@
#include "../../libsvn_client/client.h"
#include "svn_pools.h"
#include "svn_client.h"
+#include "private/svn_client_mtcc.h"
#include "svn_repos.h"
#include "svn_subst.h"
#include "private/svn_wc_private.h"
+#include "svn_props.h"
+#include "svn_hash.h"
#include "../svn_test.h"
#include "../svn_test_fs.h"
@@ -57,7 +60,8 @@ create_greek_repos(const char **repos_url,
svn_fs_root_t *txn_root;
/* Create a filesytem and repository. */
- SVN_ERR(svn_test__create_repos(&repos, name, opts, pool));
+ SVN_ERR(svn_test__create_repos(
+ &repos, svn_test_data_path(name, pool), opts, pool));
/* Prepare and commit a txn containing the Greek tree. */
SVN_ERR(svn_fs_begin_txn2(&txn, svn_repos_fs(repos), 0 /* rev */,
@@ -67,7 +71,8 @@ create_greek_repos(const char **repos_url,
SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &committed_rev, txn, pool));
SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(committed_rev));
- SVN_ERR(svn_uri_get_file_url_from_dirent(repos_url, name, pool));
+ SVN_ERR(svn_uri_get_file_url_from_dirent(
+ repos_url, svn_test_data_path(name, pool), pool));
return SVN_NO_ERROR;
}
@@ -331,7 +336,6 @@ test_patch(const svn_test_opts_t *opts,
{
const char *repos_url;
const char *wc_path;
- const char *cwd;
svn_opt_revision_t rev;
svn_opt_revision_t peg_rev;
svn_client_ctx_t *ctx;
@@ -370,12 +374,11 @@ test_patch(const svn_test_opts_t *opts,
SVN_ERR(create_greek_repos(&repos_url, "test-patch-repos", opts, pool));
/* Check out the HEAD revision */
- SVN_ERR(svn_dirent_get_absolute(&cwd, "", pool));
/* Put wc inside an unversioned directory. Checking out a 1.7 wc
directly inside a 1.6 wc doesn't work reliably, an intervening
unversioned directory prevents the problems. */
- wc_path = svn_dirent_join(cwd, "test-patch", pool);
+ wc_path = svn_test_data_path("test-patch", pool);
SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
svn_test_add_dir_cleanup(wc_path);
@@ -389,8 +392,9 @@ test_patch(const svn_test_opts_t *opts,
TRUE, FALSE, ctx, pool));
/* Create the patch file. */
- patch_file_path = svn_dirent_join_many(pool, cwd,
- "test-patch", "test-patch.diff", NULL);
+ patch_file_path = svn_dirent_join_many(
+ pool, svn_test_data_path("test-patch", pool),
+ "test-patch.diff", SVN_VA_NULL);
SVN_ERR(svn_io_file_open(&patch_file, patch_file_path,
(APR_READ | APR_WRITE | APR_CREATE | APR_TRUNCATE),
APR_OS_DEFAULT, pool));
@@ -400,7 +404,7 @@ test_patch(const svn_test_opts_t *opts,
SVN_ERR(svn_io_file_write(patch_file, unidiff_patch[i], &len, pool));
SVN_TEST_ASSERT(len == strlen(unidiff_patch[i]));
}
- SVN_ERR(svn_io_file_flush_to_disk(patch_file, pool));
+ SVN_ERR(svn_io_file_flush(patch_file, pool));
/* Apply the patch. */
pcb.patched_tempfiles = apr_hash_make(pool);
@@ -445,7 +449,7 @@ test_wc_add_scenarios(const svn_test_opts_t *opts,
SVN_ERR(create_greek_repos(&repos_url, "test-wc-add-repos", opts, pool));
committed_rev = 1;
- SVN_ERR(svn_dirent_get_absolute(&wc_path, "test-wc-add", pool));
+ wc_path = svn_test_data_path("test-wc-add", pool);
/* Remove old test data from the previous run */
SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
@@ -598,7 +602,7 @@ test_16k_add(const svn_test_opts_t *opts,
svn_opt_revision_t rev;
svn_client_ctx_t *ctx;
const char *repos_url;
- const char *cwd, *wc_path;
+ const char *wc_path;
svn_opt_revision_t peg_rev;
apr_array_header_t *targets;
apr_pool_t *iterpool = svn_pool_create(pool);
@@ -608,12 +612,11 @@ test_16k_add(const svn_test_opts_t *opts,
SVN_ERR(create_greek_repos(&repos_url, "test-16k-repos", opts, pool));
/* Check out the HEAD revision */
- SVN_ERR(svn_dirent_get_absolute(&cwd, "", pool));
/* Put wc inside an unversioned directory. Checking out a 1.7 wc
directly inside a 1.6 wc doesn't work reliably, an intervening
unversioned directory prevents the problems. */
- wc_path = svn_dirent_join(cwd, "test-16k", pool);
+ wc_path = svn_test_data_path("test-16k", pool);
SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
svn_test_add_dir_cleanup(wc_path);
@@ -735,7 +738,7 @@ test_foreign_repos_copy(const svn_test_opts_t *opts,
SVN_ERR(create_greek_repos(&repos_url, "foreign-copy1", opts, pool));
SVN_ERR(create_greek_repos(&repos2_url, "foreign-copy2", opts, pool));
- SVN_ERR(svn_dirent_get_absolute(&wc_path, "test-wc-add", pool));
+ wc_path = svn_test_data_path("test-foreign-repos-copy", pool);
wc_path = svn_dirent_join(wc_path, "foreign-wc", pool);
@@ -769,22 +772,671 @@ test_foreign_repos_copy(const svn_test_opts_t *opts,
return SVN_NO_ERROR;
}
+static svn_error_t *
+test_suggest_mergesources(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ const char *repos_url;
+ svn_client_ctx_t *ctx;
+ svn_client__mtcc_t *mtcc;
+ apr_array_header_t *results;
+ svn_opt_revision_t peg_rev;
+ svn_opt_revision_t head_rev;
+ const char *wc_path;
+
+ peg_rev.kind = svn_opt_revision_unspecified;
+
+ /* Create a filesytem and repository containing the Greek tree. */
+ SVN_ERR(create_greek_repos(&repos_url, "mergesources", opts, pool));
+
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, -1, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_copy("A", 1, "AA", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_commit(NULL, NULL, NULL, mtcc, pool));
+
+ SVN_ERR(svn_client_suggest_merge_sources(
+ &results,
+ svn_path_url_add_component2(repos_url, "AA", pool),
+ &peg_rev, ctx, pool));
+ SVN_TEST_ASSERT(results != NULL);
+ SVN_TEST_ASSERT(results->nelts >= 1);
+ SVN_TEST_STRING_ASSERT(APR_ARRAY_IDX(results, 0, const char *),
+ svn_path_url_add_component2(repos_url, "A", pool));
+
+ /* And now test the same thing with a minimal working copy */
+ wc_path = svn_test_data_path("mergesources-wc", pool);
+ svn_test_add_dir_cleanup(wc_path);
+ SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
+
+ head_rev.kind = svn_opt_revision_head;
+ SVN_ERR(svn_client_checkout3(NULL,
+ svn_path_url_add_component2(repos_url, "AA", pool),
+ wc_path,
+ &head_rev, &head_rev, svn_depth_empty,
+ FALSE, FALSE, ctx, pool));
+
+
+ SVN_ERR(svn_client_suggest_merge_sources(&results,
+ wc_path,
+ &peg_rev, ctx, pool));
+ SVN_TEST_ASSERT(results != NULL);
+ SVN_TEST_ASSERT(results->nelts >= 1);
+ SVN_TEST_STRING_ASSERT(APR_ARRAY_IDX(results, 0, const char *),
+ svn_path_url_add_component2(repos_url, "A", pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static char
+status_to_char(enum svn_wc_status_kind status)
+{
+
+ switch (status)
+ {
+ case svn_wc_status_none: return '.';
+ case svn_wc_status_unversioned: return '?';
+ case svn_wc_status_normal: return '-';
+ case svn_wc_status_added: return 'A';
+ case svn_wc_status_missing: return '!';
+ case svn_wc_status_incomplete: return ':';
+ case svn_wc_status_deleted: return 'D';
+ case svn_wc_status_replaced: return 'R';
+ case svn_wc_status_modified: return 'M';
+ case svn_wc_status_merged: return 'G';
+ case svn_wc_status_conflicted: return 'C';
+ case svn_wc_status_obstructed: return '~';
+ case svn_wc_status_ignored: return 'I';
+ case svn_wc_status_external: return 'X';
+ default: return '*';
+ }
+}
+
+static int
+compare_status_paths(const void *a, const void *b)
+{
+ const svn_client_status_t *const *const sta = a;
+ const svn_client_status_t *const *const stb = b;
+ return svn_path_compare_paths((*sta)->local_abspath, (*stb)->local_abspath);
+}
+
+static svn_error_t *
+remote_only_status_receiver(void *baton, const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *results = baton;
+ APR_ARRAY_PUSH(results, const svn_client_status_t *) =
+ svn_client_status_dup(status, results->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_remote_only_status(const svn_test_opts_t *opts, apr_pool_t *pool)
+{
+ static const struct remote_only_status_result
+ {
+ const char *relpath;
+ svn_revnum_t revision;
+ enum svn_wc_status_kind node_status;
+ enum svn_wc_status_kind text_status;
+ enum svn_wc_status_kind prop_status;
+ svn_revnum_t ood_changed_rev;
+ enum svn_wc_status_kind repos_node_status;
+ enum svn_wc_status_kind repos_text_status;
+ enum svn_wc_status_kind repos_prop_status;
+ } expected[] = {
+ { ".",
+ +1, svn_wc_status_normal, svn_wc_status_normal, svn_wc_status_none,
+ +2, svn_wc_status_modified, svn_wc_status_modified, svn_wc_status_none },
+ { "B",
+ +1, svn_wc_status_normal, svn_wc_status_normal, svn_wc_status_none,
+ +2, svn_wc_status_none, svn_wc_status_none, svn_wc_status_none },
+ { "C",
+ +1, svn_wc_status_normal, svn_wc_status_normal, svn_wc_status_none,
+ +2, svn_wc_status_deleted, svn_wc_status_none, svn_wc_status_none },
+ { "D",
+ +1, svn_wc_status_normal, svn_wc_status_normal, svn_wc_status_none,
+ +2, svn_wc_status_none, svn_wc_status_none, svn_wc_status_none },
+ { "epsilon",
+ -1, svn_wc_status_none, svn_wc_status_none, svn_wc_status_none,
+ +2, svn_wc_status_added, svn_wc_status_modified, svn_wc_status_none },
+ { "mu",
+ +1, svn_wc_status_normal, svn_wc_status_normal, svn_wc_status_none,
+ +2, svn_wc_status_modified, svn_wc_status_normal, svn_wc_status_none },
+
+ { NULL }
+ };
+
+ const char *repos_url;
+ const char *wc_path;
+ const char *local_path;
+ apr_file_t *local_file;
+ svn_client_ctx_t *ctx;
+ svn_client__mtcc_t *mtcc;
+ svn_opt_revision_t rev;
+ svn_revnum_t result_rev;
+ svn_string_t *contents = svn_string_create("modified\n", pool);
+ svn_stream_t *contentstream = svn_stream_from_string(contents, pool);
+ const struct remote_only_status_result *ex;
+ svn_stream_mark_t *start;
+ apr_array_header_t *targets;
+ apr_array_header_t *results;
+ int i;
+
+ SVN_ERR(svn_stream_mark(contentstream, &start, pool));
+
+ /* Create a filesytem and repository containing the Greek tree. */
+ SVN_ERR(create_greek_repos(&repos_url, "test-remote-only-status", opts, pool));
+
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ /* Make some modifications in the repository, creating revision 2. */
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, -1, ctx, pool, pool));
+ SVN_ERR(svn_stream_seek(contentstream, start));
+ SVN_ERR(svn_client__mtcc_add_add_file("A/epsilon", contentstream, NULL,
+ mtcc, pool));
+ SVN_ERR(svn_stream_seek(contentstream, start));
+ SVN_ERR(svn_client__mtcc_add_update_file("A/mu",
+ contentstream, NULL, NULL, NULL,
+ mtcc, pool));
+ SVN_ERR(svn_stream_seek(contentstream, start));
+ SVN_ERR(svn_client__mtcc_add_add_file("A/D/epsilon", contentstream, NULL,
+ mtcc, pool));
+ SVN_ERR(svn_stream_seek(contentstream, start));
+ SVN_ERR(svn_client__mtcc_add_update_file("A/B/lambda",
+ contentstream, NULL, NULL, NULL,
+ mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_delete("A/C", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_commit(NULL, NULL, NULL, mtcc, pool));
+
+ /* Check out a sparse root @r1 of the repository */
+ wc_path = svn_test_data_path("test-remote-only-status-wc", pool);
+ /*svn_test_add_dir_cleanup(wc_path);*/
+ SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
+
+ rev.kind = svn_opt_revision_number;
+ rev.value.number = 1;
+ SVN_ERR(svn_client_checkout3(NULL,
+ apr_pstrcat(pool, repos_url, "/A", SVN_VA_NULL),
+ wc_path, &rev, &rev, svn_depth_immediates,
+ FALSE, FALSE, ctx, pool));
+
+ /* Add a local file; this is a double-check to make sure that
+ remote-only status ignores local changes. */
+ local_path = svn_dirent_join(wc_path, "zeta", pool);
+ SVN_ERR(svn_io_file_create_empty(local_path, pool));
+ SVN_ERR(svn_client_add5(local_path, svn_depth_unknown,
+ FALSE, FALSE, FALSE, FALSE,
+ ctx, pool));
+
+ /* Replace a local dir */
+ local_path = svn_dirent_join(wc_path, "B", pool);
+ targets = apr_array_make(pool, 1, sizeof(const char*));
+ APR_ARRAY_PUSH(targets, const char*) = local_path;
+ SVN_ERR(svn_client_delete4(targets, FALSE, FALSE, NULL, NULL, NULL,
+ ctx, pool));
+ SVN_ERR(svn_client_mkdir4(targets, FALSE, NULL, NULL, NULL,
+ ctx, pool));
+
+ /* Modify a local dir's props */
+ local_path = svn_dirent_join(wc_path, "D", pool);
+ targets = apr_array_make(pool, 1, sizeof(const char*));
+ APR_ARRAY_PUSH(targets, const char*) = local_path;
+ SVN_ERR(svn_client_propset_local("prop", contents, targets,
+ svn_depth_empty, FALSE, NULL,
+ ctx, pool));
+
+ /* Modify a local file's contents */
+ local_path = svn_dirent_join(wc_path, "mu", pool);
+ SVN_ERR(svn_io_file_open(&local_file, local_path,
+ APR_FOPEN_WRITE | APR_FOPEN_TRUNCATE,
+ 0, pool));
+ SVN_ERR(svn_io_file_write_full(local_file,
+ contents->data, contents->len,
+ NULL, pool));
+ SVN_ERR(svn_io_file_close(local_file, pool));
+
+ /* Run the remote-only status. */
+ results = apr_array_make(pool, 3, sizeof(const svn_client_status_t *));
+ rev.kind = svn_opt_revision_head;
+ SVN_ERR(svn_client_status6(
+ &result_rev, ctx, wc_path, &rev, svn_depth_unknown,
+ TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, NULL,
+ remote_only_status_receiver, results, pool));
+
+ SVN_TEST_ASSERT(result_rev == 2);
+
+ /* Compare the number of results with the expected results */
+ for (i = 0, ex = expected; ex->relpath; ++ex, ++i)
+ ;
+ SVN_TEST_ASSERT(results->nelts == i);
+
+ if (opts->verbose)
+ qsort(results->elts, results->nelts, results->elt_size,
+ compare_status_paths);
+
+ for (i = 0; i < results->nelts; ++i)
+ {
+ const svn_client_status_t *st =
+ APR_ARRAY_IDX(results, i, const svn_client_status_t *);
+
+ const char *relpath =
+ svn_dirent_skip_ancestor(wc_path, st->local_abspath);
+ if (!relpath)
+ relpath = st->local_abspath;
+ if (!*relpath)
+ relpath = ".";
+
+ for (ex = expected; ex->relpath; ++ex)
+ {
+ if (0 == strcmp(relpath, ex->relpath))
+ break;
+ }
+ SVN_TEST_ASSERT(ex->relpath != NULL);
+
+ if (opts->verbose)
+ printf("%c%c%c %2ld %c%c%c %2ld %s\n",
+ status_to_char(st->node_status),
+ status_to_char(st->text_status),
+ status_to_char(st->prop_status),
+ (long)st->revision,
+ status_to_char(st->repos_node_status),
+ status_to_char(st->repos_text_status),
+ status_to_char(st->repos_prop_status),
+ (long)st->ood_changed_rev,
+ relpath);
+
+ SVN_TEST_ASSERT(st->revision == ex->revision);
+ SVN_TEST_ASSERT(st->ood_changed_rev == ex->ood_changed_rev);
+ SVN_TEST_ASSERT(st->node_status == ex->node_status);
+ SVN_TEST_ASSERT(st->repos_node_status == ex->repos_node_status);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_copy_pin_externals(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t rev;
+ svn_opt_revision_t peg_rev;
+ const char *repos_url;
+ const char *A_url;
+ const char *A_copy_url;
+ const char *wc_path;
+ svn_client_ctx_t *ctx;
+ const svn_string_t *propval;
+ apr_hash_t *externals_to_pin;
+ apr_array_header_t *external_items;
+ apr_array_header_t *copy_sources;
+ svn_wc_external_item2_t items[6];
+ svn_client_copy_source_t copy_source;
+ apr_hash_t *props;
+ apr_array_header_t *pinned_externals_descs;
+ apr_array_header_t *pinned_externals;
+ int i;
+ int num_tested_externals;
+ svn_stringbuf_t *externals_test_prop;
+ struct pin_externals_test_data {
+ const char *src_external_desc;
+ const char *expected_dst_external_desc;
+ } pin_externals_test_data[] = {
+ { "^/A/D/gamma B/gamma", "^/A/D/gamma@2 B/gamma" },
+ { "-r1 ^/A/D/G C/exdir_G", "-r1 ^/A/D/G C/exdir_G" },
+ { "^/A/D/H@1 C/exdir_H", "^/A/D/H@1 C/exdir_H" },
+ { "^/A/D/H C/exdir_H2", "^/A/D/H@2 C/exdir_H2" },
+ { "-r1 ^/A/B D/z/y/z/blah", "-r1 ^/A/B@2 D/z/y/z/blah" } ,
+ { "-r1 ^/A/D@2 exdir_D", "-r1 ^/A/D@2 exdir_D" },
+ /* Dated revision should retain their date string exactly. */
+ { "-r{1970-01-01T00:00} ^/A/C 70s", "-r{1970-01-01T00:00} ^/A/C@2 70s"},
+ { "-r{2004-02-23} ^/svn 1.0", "-r{2004-02-23} ^/svn 1.0"},
+ { NULL },
+ };
+
+ /* Create a filesytem and repository containing the Greek tree. */
+ SVN_ERR(create_greek_repos(&repos_url, "pin-externals", opts, pool));
+
+ wc_path = svn_test_data_path("pin-externals-working-copy", pool);
+
+ /* Remove old test data from the previous run */
+ SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
+
+ SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
+ svn_test_add_dir_cleanup(wc_path);
+
+ rev.kind = svn_opt_revision_head;
+ peg_rev.kind = svn_opt_revision_unspecified;
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ /* Configure some externals on ^/A */
+ i = 0;
+ externals_test_prop = svn_stringbuf_create_empty(pool);
+ while (pin_externals_test_data[i].src_external_desc)
+ {
+ svn_stringbuf_appendcstr(externals_test_prop,
+ pin_externals_test_data[i].src_external_desc);
+ svn_stringbuf_appendbyte(externals_test_prop, '\n');
+ i++;
+ }
+ propval = svn_string_create_from_buf(externals_test_prop, pool);
+ A_url = apr_pstrcat(pool, repos_url, "/A", SVN_VA_NULL);
+ SVN_ERR(svn_client_propset_remote(SVN_PROP_EXTERNALS, propval,
+ A_url, TRUE, 1, NULL,
+ NULL, NULL, ctx, pool));
+
+ /* Set up parameters for pinning some externals. */
+ externals_to_pin = apr_hash_make(pool);
+
+ items[0].url = "^/A/D/gamma";
+ items[0].target_dir = "B/gamma";
+ items[1].url = "^/A/B";
+ items[1].target_dir = "D/z/y/z/blah";
+ items[2].url = "^/A/D/H";
+ items[2].target_dir = "C/exdir_H2";
+ items[3].url= "^/A/D";
+ items[3].target_dir= "exdir_D";
+ items[4].url = "^/A/C";
+ items[4].target_dir = "70s";
+ /* Also add an entry which doesn't match any actual definition. */
+ items[5].url = "^/this/does/not/exist";
+ items[5].target_dir = "in/test/data";
+
+ external_items = apr_array_make(pool, 2, sizeof(svn_wc_external_item2_t *));
+ for (i = 0; i < sizeof(items) / sizeof(items[0]); i++)
+ APR_ARRAY_PUSH(external_items, svn_wc_external_item2_t *) = &items[i];
+ svn_hash_sets(externals_to_pin, A_url, external_items);
+
+ /* Copy ^/A to ^/A_copy, pinning two non-pinned externals. */
+ copy_source.path = A_url;
+ copy_source.revision = &rev;
+ copy_source.peg_revision = &peg_rev;
+ copy_sources = apr_array_make(pool, 1, sizeof(svn_client_copy_source_t *));
+ APR_ARRAY_PUSH(copy_sources, svn_client_copy_source_t *) = &copy_source;
+ A_copy_url = apr_pstrcat(pool, repos_url, "/A_copy", SVN_VA_NULL);
+ SVN_ERR(svn_client_copy7(copy_sources, A_copy_url, FALSE, FALSE,
+ FALSE, FALSE, TRUE, externals_to_pin,
+ NULL, NULL, NULL, ctx, pool));
+
+ /* Verify that externals were pinned as expected. */
+ SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_EXTERNALS,
+ A_copy_url, &peg_rev, &rev, NULL,
+ svn_depth_empty, NULL, ctx, pool, pool));
+ propval = svn_hash_gets(props, A_copy_url);
+ SVN_TEST_ASSERT(propval);
+
+ /* Test the unparsed representation of copied externals descriptions. */
+ pinned_externals_descs = svn_cstring_split(propval->data, "\n", FALSE, pool);
+ for (i = 0; i < pinned_externals_descs->nelts; i++)
+ {
+ const char *externals_desc;
+ const char *expected_desc;
+
+ externals_desc = APR_ARRAY_IDX(pinned_externals_descs, i, const char *);
+ expected_desc = pin_externals_test_data[i].expected_dst_external_desc;
+ SVN_TEST_STRING_ASSERT(externals_desc, expected_desc);
+ }
+ /* Ensure all test cases were tested. */
+ SVN_TEST_ASSERT(i == (sizeof(pin_externals_test_data) /
+ sizeof(pin_externals_test_data[0]) - 1));
+
+ SVN_ERR(svn_wc_parse_externals_description3(&pinned_externals, A_copy_url,
+ propval->data, TRUE, pool));
+
+ /* For completeness, test the parsed representation, too */
+ num_tested_externals = 0;
+ for (i = 0; i < pinned_externals->nelts; i++)
+ {
+ svn_wc_external_item2_t *item;
+
+ item = APR_ARRAY_IDX(pinned_externals, i, svn_wc_external_item2_t *);
+ if (strcmp(item->url, "^/A/D/gamma") == 0)
+ {
+ SVN_TEST_STRING_ASSERT(item->target_dir, "B/gamma");
+ /* Pinned to r2. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->revision.value.number == 2);
+ SVN_TEST_ASSERT(item->peg_revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->peg_revision.value.number == 2);
+ num_tested_externals++;
+ }
+ else if (strcmp(item->url, "^/A/D/G") == 0)
+ {
+ SVN_TEST_STRING_ASSERT(item->target_dir, "C/exdir_G");
+ /* Not pinned. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->revision.value.number == 1);
+ SVN_TEST_ASSERT(item->peg_revision.kind == svn_opt_revision_head);
+ num_tested_externals++;
+ }
+ else if (strcmp(item->url, "^/A/D/H") == 0)
+ {
+ if (strcmp(item->target_dir, "C/exdir_H") == 0)
+ {
+ /* Was already pinned to r1. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->revision.value.number == 1);
+ SVN_TEST_ASSERT(item->peg_revision.kind ==
+ svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->peg_revision.value.number == 1);
+ num_tested_externals++;
+ }
+ else if (strcmp(item->target_dir, "C/exdir_H2") == 0)
+ {
+ /* Pinned to r2. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->revision.value.number == 2);
+ SVN_TEST_ASSERT(item->peg_revision.kind ==
+ svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->peg_revision.value.number == 2);
+ num_tested_externals++;
+ }
+ else
+ SVN_TEST_ASSERT(FALSE); /* unknown external */
+ }
+ else if (strcmp(item->url, "^/A/B") == 0)
+ {
+ SVN_TEST_STRING_ASSERT(item->target_dir, "D/z/y/z/blah");
+ /* Pinned to r2. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->revision.value.number == 1);
+ SVN_TEST_ASSERT(item->peg_revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->peg_revision.value.number == 2);
+ num_tested_externals++;
+ }
+ else if (strcmp(item->url, "^/A/D") == 0)
+ {
+ SVN_TEST_STRING_ASSERT(item->target_dir, "exdir_D");
+ /* Pinned to r2. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->revision.value.number == 1);
+ SVN_TEST_ASSERT(item->peg_revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->peg_revision.value.number == 2);
+ num_tested_externals++;
+ }
+ else if (strcmp(item->url, "^/A/C") == 0)
+ {
+ SVN_TEST_STRING_ASSERT(item->target_dir, "70s");
+ /* Pinned to r2. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_date);
+ /* Don't bother testing the exact date value here. */
+ SVN_TEST_ASSERT(item->peg_revision.kind == svn_opt_revision_number);
+ SVN_TEST_ASSERT(item->peg_revision.value.number == 2);
+ num_tested_externals++;
+ }
+ else if (strcmp(item->url, "^/svn") == 0)
+ {
+ SVN_TEST_STRING_ASSERT(item->target_dir, "1.0");
+ /* Was and not in externals_to_pin, operative revision was a date. */
+ SVN_TEST_ASSERT(item->revision.kind == svn_opt_revision_date);
+ /* Don't bother testing the exact date value here. */
+ SVN_TEST_ASSERT(item->peg_revision.kind == svn_opt_revision_head);
+ num_tested_externals++;
+ }
+ else
+ SVN_TEST_ASSERT(FALSE); /* unknown URL */
+ }
+
+ /* Ensure all test cases were tested. */
+ SVN_TEST_ASSERT(num_tested_externals == (sizeof(pin_externals_test_data) /
+ sizeof(pin_externals_test_data[0])
+ - 1));
+
+ return SVN_NO_ERROR;
+}
+
+/* issue #4560 */
+static svn_error_t *
+test_copy_pin_externals_select_subtree(const svn_test_opts_t *opts, apr_pool_t *pool)
+{
+ svn_opt_revision_t rev;
+ svn_opt_revision_t peg_rev;
+ const char *repos_url;
+ const char *A_copy_url;
+ const char *B_url;
+ const char *wc_path;
+ svn_client_ctx_t *ctx;
+ apr_hash_t *externals_to_pin;
+ apr_array_header_t *external_items;
+ apr_array_header_t *copy_sources;
+ svn_wc_external_item2_t item;
+ svn_client_copy_source_t copy_source;
+ apr_hash_t *props;
+ int i;
+ struct test_data {
+ const char *subtree_relpath;
+ const char *src_external_desc;
+ const char *expected_dst_external_desc;
+ } test_data[] = {
+ /* Note: these externals definitions contain extra whitespace on
+ purpose, to test that the pinning logic doesn't make
+ whitespace-only changes to values that aren't pinned. */
+
+ /* External on A/B will be pinned. */
+ { "B", "^/A/D/gamma gamma-ext", "^/A/D/gamma@3 gamma-ext" },
+
+ /* External on A/D won't be pinned. */
+ { "D", "^/A/B/F F-ext", "^/A/B/F F-ext" } ,
+
+ { NULL },
+ };
+
+ /* Create a filesytem and repository containing the Greek tree. */
+ SVN_ERR(create_greek_repos(&repos_url, "pin-externals-select-subtree",
+ opts, pool));
+
+ wc_path = svn_test_data_path("pin-externals-select-subtree-wc", pool);
+
+ /* Remove old test data from the previous run */
+ SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
+
+ SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
+ svn_test_add_dir_cleanup(wc_path);
+
+ rev.kind = svn_opt_revision_head;
+ peg_rev.kind = svn_opt_revision_unspecified;
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ /* Configure externals. */
+ i = 0;
+ while (test_data[i].subtree_relpath)
+ {
+ const char *subtree_relpath;
+ const char *url;
+ const svn_string_t *propval;
+
+ subtree_relpath = test_data[i].subtree_relpath;
+ propval = svn_string_create(test_data[i].src_external_desc, pool);
+
+ url = apr_pstrcat(pool, repos_url, "/A/", subtree_relpath, SVN_VA_NULL);
+ SVN_ERR(svn_client_propset_remote(SVN_PROP_EXTERNALS, propval,
+ url, TRUE, 1, NULL,
+ NULL, NULL, ctx, pool));
+ i++;
+ }
+
+ /* Set up parameters for pinning externals on A/B. */
+ externals_to_pin = apr_hash_make(pool);
+
+ item.url = "^/A/D/gamma";
+ item.target_dir = "gamma-ext";
+
+ external_items = apr_array_make(pool, 2, sizeof(svn_wc_external_item2_t *));
+ APR_ARRAY_PUSH(external_items, svn_wc_external_item2_t *) = &item;
+ B_url = apr_pstrcat(pool, repos_url, "/A/B", SVN_VA_NULL);
+ svn_hash_sets(externals_to_pin, B_url, external_items);
+
+ /* Copy ^/A to ^/A_copy, pinning externals on ^/A/B. */
+ copy_source.path = apr_pstrcat(pool, repos_url, "/A", SVN_VA_NULL);
+ copy_source.revision = &rev;
+ copy_source.peg_revision = &peg_rev;
+ copy_sources = apr_array_make(pool, 1, sizeof(svn_client_copy_source_t *));
+ APR_ARRAY_PUSH(copy_sources, svn_client_copy_source_t *) = &copy_source;
+ A_copy_url = apr_pstrcat(pool, repos_url, "/A_copy", SVN_VA_NULL);
+ SVN_ERR(svn_client_copy7(copy_sources, A_copy_url, FALSE, FALSE,
+ FALSE, FALSE, TRUE, externals_to_pin,
+ NULL, NULL, NULL, ctx, pool));
+
+ /* Verify that externals were pinned as expected. */
+ i = 0;
+ while (test_data[i].subtree_relpath)
+ {
+ const char *subtree_relpath;
+ const char *url;
+ const svn_string_t *propval;
+ svn_stringbuf_t *externals_desc;
+ const char *expected_desc;
+
+ subtree_relpath = test_data[i].subtree_relpath;
+ url = apr_pstrcat(pool, A_copy_url, "/", subtree_relpath, SVN_VA_NULL);
+
+ SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_EXTERNALS,
+ url, &peg_rev, &rev, NULL,
+ svn_depth_empty, NULL, ctx, pool, pool));
+ propval = svn_hash_gets(props, url);
+ SVN_TEST_ASSERT(propval);
+ externals_desc = svn_stringbuf_create(propval->data, pool);
+ svn_stringbuf_strip_whitespace(externals_desc);
+ expected_desc = test_data[i].expected_dst_external_desc;
+ SVN_TEST_STRING_ASSERT(externals_desc->data, expected_desc);
+
+ i++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* ========================================================================== */
-struct svn_test_descriptor_t test_funcs[] =
+
+static int max_threads = 3;
+
+static struct svn_test_descriptor_t test_funcs[] =
{
SVN_TEST_NULL,
SVN_TEST_PASS2(test_elide_mergeinfo_catalog,
"test svn_client__elide_mergeinfo_catalog"),
SVN_TEST_PASS2(test_args_to_target_array,
"test svn_client_args_to_target_array"),
- SVN_TEST_OPTS_PASS(test_patch, "test svn_client_patch"),
SVN_TEST_OPTS_PASS(test_wc_add_scenarios, "test svn_wc_add3 scenarios"),
+ SVN_TEST_OPTS_PASS(test_foreign_repos_copy, "test foreign repository copy"),
+ SVN_TEST_OPTS_PASS(test_patch, "test svn_client_patch"),
SVN_TEST_OPTS_PASS(test_copy_crash, "test a crash in svn_client_copy5"),
#ifdef TEST16K_ADD
SVN_TEST_OPTS_PASS(test_16k_add, "test adding 16k files"),
#endif
SVN_TEST_OPTS_PASS(test_youngest_common_ancestor, "test youngest_common_ancestor"),
- SVN_TEST_OPTS_PASS(test_foreign_repos_copy, "test foreign repository copy"),
+ SVN_TEST_OPTS_PASS(test_suggest_mergesources,
+ "test svn_client_suggest_merge_sources"),
+ SVN_TEST_OPTS_PASS(test_remote_only_status,
+ "test svn_client_status6 with ignore_local_mods"),
+ SVN_TEST_OPTS_PASS(test_copy_pin_externals,
+ "test svn_client_copy7 with externals_to_pin"),
+ SVN_TEST_OPTS_PASS(test_copy_pin_externals_select_subtree,
+ "pin externals on selected subtrees only"),
SVN_TEST_NULL
};
+
+SVN_TEST_MAIN
diff --git a/subversion/tests/libsvn_client/mtcc-test.c b/subversion/tests/libsvn_client/mtcc-test.c
new file mode 100644
index 0000000..e11738e
--- /dev/null
+++ b/subversion/tests/libsvn_client/mtcc-test.c
@@ -0,0 +1,817 @@
+/*
+ * Regression tests for mtcc code in the libsvn_client library.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_client.h"
+#include "private/svn_client_mtcc.h"
+
+#include "../svn_test.h"
+#include "../svn_test_fs.h"
+
+/* Baton for verify_commit_callback*/
+struct verify_commit_baton
+{
+ const svn_commit_info_t *commit_info;
+ apr_pool_t *result_pool;
+};
+
+/* Commit result collector for verify_mtcc_commit */
+static svn_error_t *
+verify_commit_callback(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct verify_commit_baton *vcb = baton;
+
+ vcb->commit_info = svn_commit_info_dup(commit_info, vcb->result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Create a stream from a c string */
+static svn_stream_t *
+cstr_stream(const char *data, apr_pool_t *result_pool)
+{
+ return svn_stream_from_string(svn_string_create(data, result_pool),
+ result_pool);
+}
+
+static svn_error_t *
+verify_mtcc_commit(svn_client__mtcc_t *mtcc,
+ svn_revnum_t expected_rev,
+ apr_pool_t *pool)
+{
+ struct verify_commit_baton vcb;
+ vcb.commit_info = NULL;
+ vcb.result_pool = pool;
+
+ SVN_ERR(svn_client__mtcc_commit(NULL, verify_commit_callback, &vcb, mtcc, pool));
+
+ SVN_TEST_ASSERT(vcb.commit_info != NULL);
+ SVN_TEST_ASSERT(vcb.commit_info->revision == expected_rev);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Constructs a greek tree as revision 1 in the repository at repos_url */
+static svn_error_t *
+make_greek_tree(const char *repos_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *subpool;
+ int i;
+
+ subpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, subpool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, subpool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 0, ctx, subpool, subpool));
+
+ for (i = 0; svn_test__greek_tree_nodes[i].path; i++)
+ {
+ if (svn_test__greek_tree_nodes[i].contents)
+ {
+ SVN_ERR(svn_client__mtcc_add_add_file(
+ svn_test__greek_tree_nodes[i].path,
+ cstr_stream(
+ svn_test__greek_tree_nodes[i].contents,
+ subpool),
+ NULL /* src_checksum */,
+ mtcc, subpool));
+ }
+ else
+ {
+ SVN_ERR(svn_client__mtcc_add_mkdir(
+ svn_test__greek_tree_nodes[i].path,
+ mtcc, subpool));
+ }
+ }
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 1, subpool));
+
+ svn_pool_clear(subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_mkdir(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-mkdir",
+ opts, pool, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 0, ctx, pool, pool));
+
+ SVN_ERR(svn_client__mtcc_add_mkdir("branches", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("trunk", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("branches/1.x", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("tags", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("tags/1.0", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("tags/1.1", mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 1, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_mkgreek(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-mkgreek",
+ opts, pool, pool));
+
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+ SVN_ERR(svn_client__mtcc_add_copy("A", 1, "greek_A", mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_swap(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-swap",
+ opts, pool, pool));
+
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+ SVN_ERR(svn_client__mtcc_add_move("A/B", "B", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_move("A/D", "A/B", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_copy("A/B", 1, "A/D", mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_propset(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-propset",
+ opts, pool, pool));
+
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+ SVN_ERR(svn_client__mtcc_add_propset("iota", "key",
+ svn_string_create("val", pool), FALSE,
+ mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_propset("A", "A-key",
+ svn_string_create("val-A", pool), FALSE,
+ mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_propset("A/B", "B-key",
+ svn_string_create("val-B", pool), FALSE,
+ mtcc, pool));
+
+ /* The repository ignores propdeletes of properties that aren't there,
+ so this just works */
+ SVN_ERR(svn_client__mtcc_add_propset("A/D", "D-key", NULL, FALSE,
+ mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 2, ctx, pool, pool));
+ SVN_TEST_ASSERT_ERROR(
+ svn_client__mtcc_add_propset("A", SVN_PROP_MIME_TYPE,
+ svn_string_create("text/plain", pool),
+ FALSE, mtcc, pool),
+ SVN_ERR_ILLEGAL_TARGET);
+
+ SVN_TEST_ASSERT_ERROR(
+ svn_client__mtcc_add_propset("iota", SVN_PROP_IGNORE,
+ svn_string_create("iota", pool),
+ FALSE, mtcc, pool),
+ SVN_ERR_ILLEGAL_TARGET);
+
+ SVN_ERR(svn_client__mtcc_add_propset("iota", SVN_PROP_EOL_STYLE,
+ svn_string_create("LF", pool),
+ FALSE, mtcc, pool));
+
+ SVN_ERR(svn_client__mtcc_add_add_file("ok", cstr_stream("line\nline\n", pool),
+ NULL, mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_add_file("bad", cstr_stream("line\nno\r\n", pool),
+ NULL, mtcc, pool));
+
+ SVN_ERR(svn_client__mtcc_add_propset("ok", SVN_PROP_EOL_STYLE,
+ svn_string_create("LF", pool),
+ FALSE, mtcc, pool));
+
+ SVN_TEST_ASSERT_ERROR(
+ svn_client__mtcc_add_propset("bad", SVN_PROP_EOL_STYLE,
+ svn_string_create("LF", pool),
+ FALSE, mtcc, pool),
+ SVN_ERR_ILLEGAL_TARGET);
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 3, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_update_files(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-update-files",
+ opts, pool, pool));
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+ /* Update iota with knowledge of the old data */
+ SVN_ERR(svn_client__mtcc_add_update_file(svn_test__greek_tree_nodes[0].path,
+ cstr_stream("new-iota", pool),
+ NULL,
+ cstr_stream(
+ svn_test__greek_tree_nodes[0]
+ .contents,
+ pool),
+ NULL,
+ mtcc, pool));
+
+ SVN_ERR(svn_client__mtcc_add_update_file("A/mu",
+ cstr_stream("new-MU", pool),
+ NULL,
+ NULL, NULL,
+ mtcc, pool));
+
+ /* Set a property on the same node */
+ SVN_ERR(svn_client__mtcc_add_propset("A/mu", "mu-key",
+ svn_string_create("mu-A", pool), FALSE,
+ mtcc, pool));
+ /* And some other node */
+ SVN_ERR(svn_client__mtcc_add_propset("A/B", "B-key",
+ svn_string_create("val-B", pool), FALSE,
+ mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_overwrite(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-overwrite",
+ opts, pool, pool));
+
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+ SVN_ERR(svn_client__mtcc_add_copy("A", 1, "AA", mtcc, pool));
+
+ SVN_TEST_ASSERT_ERROR(svn_client__mtcc_add_mkdir("AA/B", mtcc, pool),
+ SVN_ERR_FS_ALREADY_EXISTS);
+
+ SVN_TEST_ASSERT_ERROR(svn_client__mtcc_add_mkdir("AA/D/H/chi", mtcc, pool),
+ SVN_ERR_FS_ALREADY_EXISTS);
+
+ SVN_ERR(svn_client__mtcc_add_mkdir("AA/BB", mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_anchoring(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-anchoring",
+ opts, pool, pool));
+
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ /* Update a file as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc,
+ svn_path_url_add_component2(repos_url, "iota",
+ pool),
+ 1, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_update_file("",
+ cstr_stream("new-iota", pool),
+ NULL, NULL, NULL,
+ mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_propset("", "key",
+ svn_string_create("value", pool),
+ FALSE, mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+
+ /* Add a directory as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc,
+ svn_path_url_add_component2(repos_url, "BB",
+ pool),
+ 2, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("", mtcc, pool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 3, pool));
+
+ /* Add a file as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc,
+ svn_path_url_add_component2(repos_url, "new",
+ pool),
+ 3, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_add_file("", cstr_stream("new", pool), NULL,
+ mtcc, pool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 4, pool));
+
+ /* Delete as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc,
+ svn_path_url_add_component2(repos_url, "new",
+ pool),
+ 4, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_delete("", mtcc, pool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 5, pool));
+
+ /* Propset file as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc,
+ svn_path_url_add_component2(repos_url, "A/mu",
+ pool),
+ 5, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_propset("", "key",
+ svn_string_create("val", pool),
+ FALSE, mtcc, pool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 6, pool));
+
+ /* Propset dir as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc,
+ svn_path_url_add_component2(repos_url, "A",
+ pool),
+ 6, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_propset("", "key",
+ svn_string_create("val", pool),
+ FALSE, mtcc, pool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 7, pool));
+
+ /* Propset reposroot as root operation */
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 7, ctx, pool, pool));
+ SVN_ERR(svn_client__mtcc_add_propset("", "key",
+ svn_string_create("val", pool),
+ FALSE, mtcc, pool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 8, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_replace_tree(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ const char *repos_url;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-replace_tree",
+ opts, pool, pool));
+
+ SVN_ERR(make_greek_tree(repos_url, pool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, pool, pool));
+
+ SVN_ERR(svn_client__mtcc_add_delete("A", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_delete("iota", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A/B", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A/B/C", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("M", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("M/N", mtcc, pool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("M/N/O", mtcc, pool));
+
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for handle_rev */
+struct handle_rev_baton
+{
+ svn_revnum_t last;
+ svn_boolean_t up;
+ svn_boolean_t first;
+
+ /* Per revision handler */
+ svn_txdelta_window_handler_t inner_handler;
+ void *inner_baton;
+
+ /* Swapped between revisions to reconstruct data */
+ svn_stringbuf_t *cur;
+ svn_stringbuf_t *prev;
+
+ /* Pool for some test stuff */
+ apr_pool_t *pool;
+};
+
+/* Implement svn_txdelta_window_handler_t */
+static svn_error_t *
+handle_rev_delta(svn_txdelta_window_t *window,
+ void * baton)
+{
+ struct handle_rev_baton *hrb = baton;
+
+ SVN_ERR(hrb->inner_handler(window, hrb->inner_baton));
+
+ if (!window)
+ {
+ int expected_rev;
+ const char *expected;
+
+ /* Some revisions don't update the revision body */
+ switch (hrb->last)
+ {
+ case 5:
+ expected_rev = 4;
+ break;
+ case 7: /* Not reported */
+ case 8:
+ expected_rev = 6;
+ break;
+ default:
+ expected_rev = (int)hrb->last;
+ }
+
+ expected = apr_psprintf(hrb->pool, "revision-%d", expected_rev);
+
+ SVN_TEST_STRING_ASSERT(hrb->cur->data, expected);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for test_file_revs_both_ways */
+static svn_error_t *
+handle_rev(void *baton,
+ const char *path,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ svn_boolean_t result_of_merge,
+ svn_txdelta_window_handler_t *delta_handler,
+ void **delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool)
+{
+ struct handle_rev_baton *hrb = baton;
+ svn_revnum_t expected_rev = hrb->up ? (hrb->last + 1) : (hrb->last - 1);
+
+ if (expected_rev == 7)
+ expected_rev = hrb->up ? 8 : 6;
+
+ SVN_TEST_ASSERT(rev == expected_rev);
+ SVN_TEST_ASSERT(apr_hash_count(rev_props) >= 3);
+ SVN_TEST_STRING_ASSERT(path, (rev < 5) ? "/iota" : "/mu");
+
+ if (!hrb->first
+ && (rev == (hrb->up ? 5 : 4) || rev == (hrb->up ? 8 : 6)))
+ SVN_TEST_ASSERT(delta_handler == NULL);
+ else
+ SVN_TEST_ASSERT(delta_handler != NULL);
+
+ if (delta_handler)
+ {
+ svn_stringbuf_t *tmp;
+
+ *delta_handler = handle_rev_delta;
+ *delta_baton = hrb;
+
+ /* Swap string buffers, to use previous as original */
+ tmp = hrb->prev;
+ hrb->prev = hrb->cur;
+ hrb->cur = tmp;
+
+ svn_stringbuf_setempty(hrb->cur);
+
+ svn_txdelta_apply(svn_stream_from_stringbuf(hrb->prev, pool),
+ svn_stream_from_stringbuf(hrb->cur, pool),
+ NULL, NULL, pool,
+ &hrb->inner_handler,
+ &hrb->inner_baton);
+ }
+
+ hrb->last = rev;
+ hrb->first = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_file_revs_both_ways(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *repos_url;
+ svn_ra_session_t *ra;
+ struct handle_rev_baton hrb;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-file-revs",
+ opts, pool, subpool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 0, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_add_file("iota",
+ cstr_stream("revision-1", subpool),
+ NULL /* src_checksum */,
+ mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 1, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 1, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_update_file("iota",
+ cstr_stream("revision-2", subpool),
+ NULL /* src_checksum */, NULL, NULL,
+ mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 2, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 2, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_update_file("iota",
+ cstr_stream("revision-3", subpool),
+ NULL /* src_checksum */, NULL, NULL,
+ mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 3, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 3, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_update_file("iota",
+ cstr_stream("revision-4", subpool),
+ NULL /* src_checksum */, NULL, NULL,
+ mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 4, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 4, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_move("iota", "mu", mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 5, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 5, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_update_file("mu",
+ cstr_stream("revision-6", subpool),
+ NULL /* src_checksum */, NULL, NULL,
+ mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 6, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 6, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_delete("mu", mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 7, subpool));
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_client_open_ra_session2(&ra, repos_url, NULL, ctx, pool, subpool));
+
+ hrb.prev = svn_stringbuf_create("", pool);
+ hrb.cur = svn_stringbuf_create("", pool);
+ hrb.pool = pool;
+
+ svn_pool_clear(subpool);
+ hrb.up = FALSE;
+ hrb.last = 5;
+ hrb.first = TRUE;
+ svn_stringbuf_setempty(hrb.prev);
+ svn_stringbuf_setempty(hrb.cur);
+ SVN_ERR(svn_ra_get_file_revs2(ra, "iota", 4, 1, FALSE,
+ handle_rev, &hrb,
+ subpool));
+ SVN_TEST_ASSERT(hrb.last == 1);
+
+ svn_pool_clear(subpool);
+ hrb.up = TRUE;
+ hrb.last = 0;
+ hrb.first = TRUE;
+ svn_stringbuf_setempty(hrb.prev);
+ svn_stringbuf_setempty(hrb.cur);
+ SVN_ERR(svn_ra_get_file_revs2(ra, "iota", 1, 4, FALSE,
+ handle_rev, &hrb,
+ subpool));
+ SVN_TEST_ASSERT(hrb.last == 4);
+
+ svn_pool_clear(subpool);
+ hrb.up = FALSE;
+ hrb.last = 7;
+ hrb.first = TRUE;
+ svn_stringbuf_setempty(hrb.prev);
+ svn_stringbuf_setempty(hrb.cur);
+ SVN_ERR(svn_ra_get_file_revs2(ra, "mu", 6, 1, FALSE,
+ handle_rev, &hrb,
+ subpool));
+ SVN_TEST_ASSERT(hrb.last == 1);
+
+ svn_pool_clear(subpool);
+ hrb.up = TRUE;
+ hrb.last = 0;
+ hrb.first = TRUE;
+ svn_stringbuf_setempty(hrb.prev);
+ svn_stringbuf_setempty(hrb.cur);
+ SVN_ERR(svn_ra_get_file_revs2(ra, "mu", 1, 6, FALSE,
+ handle_rev, &hrb,
+ subpool));
+ SVN_TEST_ASSERT(hrb.last == 6);
+
+ /* Ressurect mu */
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 7, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_copy("mu", 6, "mu", mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 8, subpool));
+
+ svn_pool_clear(subpool);
+ hrb.up = TRUE;
+ hrb.last = 0;
+ hrb.first = TRUE;
+ svn_stringbuf_setempty(hrb.prev);
+ svn_stringbuf_setempty(hrb.cur);
+ SVN_ERR(svn_ra_get_file_revs2(ra, "mu", 1, SVN_INVALID_REVNUM, FALSE,
+ handle_rev, &hrb,
+ subpool));
+ SVN_TEST_ASSERT(hrb.last == 8);
+
+ svn_pool_clear(subpool);
+ hrb.up = FALSE;
+ hrb.last = 9;
+ hrb.first = TRUE;
+ svn_stringbuf_setempty(hrb.prev);
+ svn_stringbuf_setempty(hrb.cur);
+ SVN_ERR(svn_ra_get_file_revs2(ra, "mu", SVN_INVALID_REVNUM, 1, FALSE,
+ handle_rev, &hrb,
+ subpool));
+ SVN_TEST_ASSERT(hrb.last == 1);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_iprops_path_format(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_client__mtcc_t *mtcc;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *repos_url;
+ svn_ra_session_t *ra;
+
+ SVN_ERR(svn_test__create_repos2(NULL, &repos_url, NULL, "mtcc-iprops-paths",
+ opts, pool, subpool));
+
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ SVN_ERR(svn_test__init_auth_baton(&ctx->auth_baton, pool));
+
+ SVN_ERR(svn_client__mtcc_create(&mtcc, repos_url, 0, ctx, subpool, subpool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A", mtcc, subpool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A/B", mtcc, subpool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A/B/C", mtcc, subpool));
+ SVN_ERR(svn_client__mtcc_add_mkdir("A/B/C/D", mtcc, subpool));
+ SVN_ERR(svn_client__mtcc_add_propset("", "on-root",
+ svn_string_create("ROOT", subpool),
+ FALSE, mtcc, subpool));
+ SVN_ERR(svn_client__mtcc_add_propset("A/B", "on-B",
+ svn_string_create("BBBB", subpool),
+ FALSE, mtcc, subpool));
+ SVN_ERR(svn_client__mtcc_add_propset("A/B/C", "Z",
+ svn_string_create("Z", subpool),
+ FALSE, mtcc, subpool));
+ SVN_ERR(verify_mtcc_commit(mtcc, 1, subpool));
+ svn_pool_clear(subpool);
+
+ {
+ apr_array_header_t *iprops;
+ svn_prop_inherited_item_t *ip;
+
+ SVN_ERR(svn_client_open_ra_session2(&ra, repos_url, NULL, ctx,
+ pool, subpool));
+
+ SVN_ERR(svn_ra_get_inherited_props(ra, &iprops, "A/B/C/D", 1,
+ subpool, subpool));
+
+ SVN_TEST_ASSERT(iprops != NULL);
+ SVN_TEST_INT_ASSERT(iprops->nelts, 3);
+
+ ip = APR_ARRAY_IDX(iprops, 0, svn_prop_inherited_item_t *);
+ SVN_TEST_STRING_ASSERT(ip->path_or_url, "");
+
+ ip = APR_ARRAY_IDX(iprops, 1, svn_prop_inherited_item_t *);
+ SVN_TEST_STRING_ASSERT(ip->path_or_url, "A/B");
+
+ ip = APR_ARRAY_IDX(iprops, 2, svn_prop_inherited_item_t *);
+ SVN_TEST_STRING_ASSERT(ip->path_or_url, "A/B/C");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* ========================================================================== */
+
+
+static int max_threads = 3;
+
+static struct svn_test_descriptor_t test_funcs[] =
+ {
+ SVN_TEST_NULL,
+ SVN_TEST_OPTS_PASS(test_mkdir,
+ "test mtcc mkdir"),
+ SVN_TEST_OPTS_PASS(test_mkgreek,
+ "test making greek tree"),
+ SVN_TEST_OPTS_PASS(test_swap,
+ "swapping some trees"),
+ SVN_TEST_OPTS_PASS(test_propset,
+ "test propset and propdel"),
+ SVN_TEST_OPTS_PASS(test_update_files,
+ "test update files"),
+ SVN_TEST_OPTS_PASS(test_overwrite,
+ "test overwrite"),
+ SVN_TEST_OPTS_PASS(test_anchoring,
+ "test mtcc anchoring for root operations"),
+ SVN_TEST_OPTS_PASS(test_replace_tree,
+ "test mtcc replace tree"),
+ SVN_TEST_OPTS_PASS(test_file_revs_both_ways,
+ "test ra_get_file_revs2 both ways"),
+ SVN_TEST_OPTS_PASS(test_iprops_path_format,
+ "test iprops url format"),
+ SVN_TEST_NULL
+ };
+
+SVN_TEST_MAIN