blob: bc97b9a63629bfb1def6f1fc3731e3d86b12f07b [file] [log] [blame]
/*
* Regression tests for logic 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.
* ====================================================================
*/
#define SVN_DEPRECATED
#include <limits.h>
#include "svn_mergeinfo.h"
#include "../../libsvn_client/mergeinfo.h"
#include "../../libsvn_client/client.h"
#include "svn_pools.h"
#include "svn_client.h"
#include "private/svn_client_private.h"
#include "private/svn_client_mtcc.h"
#include "svn_repos.h"
#include "svn_subst.h"
#include "private/svn_sorts_private.h"
#include "private/svn_wc_private.h"
#include "svn_props.h"
#include "svn_hash.h"
#include "../svn_test.h"
#include "../svn_test_fs.h"
/* Create a repository with a filesystem based on OPTS in a subdir NAME,
* commit the standard Greek tree as revision 1, and set *REPOS_URL to
* the URL we will use to access it.
*
* ### This always returns a file: URL. We should upgrade this to use the
* test suite's specified URL scheme instead. */
static svn_error_t *
create_greek_repos(const char **repos_url,
const char *name,
const svn_test_opts_t *opts,
apr_pool_t *pool)
{
svn_repos_t *repos;
svn_revnum_t committed_rev;
svn_fs_txn_t *txn;
svn_fs_root_t *txn_root;
/* Create a filesystem and repository. */
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 */,
0 /* flags */, pool));
SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
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, svn_test_data_path(name, pool), pool));
return SVN_NO_ERROR;
}
typedef struct mergeinfo_catalog_item {
const char *path;
const char *unparsed_mergeinfo;
svn_boolean_t remains;
} mergeinfo_catalog_item;
#define MAX_ITEMS 10
static mergeinfo_catalog_item elide_testcases[][MAX_ITEMS] = {
{ {"/foo", "/bar: 1-4", TRUE},
{"/foo/beep/baz", "/bar/beep/baz: 1-4", FALSE},
{ NULL }},
{ {"/foo", "/bar: 1-4", TRUE},
{"/foo/beep/baz", "/blaa/beep/baz: 1-4", TRUE},
{ NULL }},
{ {"/", "/gah: 1-4", TRUE},
{"/foo/beep/baz", "/gah/foo/beep/baz: 1-4", FALSE},
{ NULL }}
};
static svn_error_t *
test_elide_mergeinfo_catalog(apr_pool_t *pool)
{
int i;
apr_pool_t *iterpool;
iterpool = svn_pool_create(pool);
for (i = 0;
i < sizeof(elide_testcases) / sizeof(elide_testcases[0]);
i++)
{
svn_mergeinfo_catalog_t mergeinfo_catalog;
mergeinfo_catalog_item *item;
svn_pool_clear(iterpool);
mergeinfo_catalog = apr_hash_make(iterpool);
for (item = elide_testcases[i]; item->path; item++)
{
svn_mergeinfo_t mergeinfo;
SVN_ERR(svn_mergeinfo_parse(&mergeinfo, item->unparsed_mergeinfo,
iterpool));
apr_hash_set(mergeinfo_catalog, item->path, APR_HASH_KEY_STRING,
mergeinfo);
}
SVN_ERR(svn_client__elide_mergeinfo_catalog(mergeinfo_catalog,
iterpool));
for (item = elide_testcases[i]; item->path; item++)
{
apr_hash_t *mergeinfo = apr_hash_get(mergeinfo_catalog, item->path,
APR_HASH_KEY_STRING);
if (item->remains && !mergeinfo)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"Elision for test case #%d incorrectly "
"elided '%s'", i, item->path);
if (!item->remains && mergeinfo)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"Elision for test case #%d failed to "
"elide '%s'", i, item->path);
}
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
test_args_to_target_array(apr_pool_t *pool)
{
apr_size_t i;
apr_pool_t *iterpool;
svn_client_ctx_t *ctx;
static struct {
const char *input;
const char *output; /* NULL means an error is expected. */
} const tests[] = {
{ ".", "" },
{ ".@BASE", "@BASE" },
{ "foo///bar", "foo/bar" },
{ "foo///bar@13", "foo/bar@13" },
{ "foo///bar@HEAD", "foo/bar@HEAD" },
{ "foo///bar@{1999-12-31}", "foo/bar@{1999-12-31}" },
{ "http://a//b////", "http://a/b" },
{ "http://a///b@27", "http://a/b@27" },
{ "http://a/b//@COMMITTED", "http://a/b@COMMITTED" },
{ "foo///bar@1:2", "foo/bar@1:2" },
{ "foo///bar@baz", "foo/bar@baz" },
{ "foo///bar@", "foo/bar@" },
{ "foo///bar///@13", "foo/bar@13" },
{ "foo///bar@@13", "foo/bar@@13" },
{ "foo///@bar@HEAD", "foo/@bar@HEAD" },
{ "foo@///bar", "foo@/bar" },
{ "foo@HEAD///bar", "foo@HEAD/bar" },
};
SVN_ERR(svn_client_create_context(&ctx, pool));
iterpool = svn_pool_create(pool);
for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++)
{
const char *input = tests[i].input;
const char *expected_output = tests[i].output;
apr_array_header_t *targets;
apr_getopt_t *os;
const int argc = 2;
const char *argv[3] = { 0 };
apr_status_t apr_err;
svn_error_t *err;
argv[0] = "opt-test";
argv[1] = input;
argv[2] = NULL;
apr_err = apr_getopt_init(&os, iterpool, argc, argv);
if (apr_err)
return svn_error_wrap_apr(apr_err,
"Error initializing command line arguments");
err = svn_client_args_to_target_array2(&targets, os, NULL, ctx, FALSE,
iterpool);
if (expected_output)
{
const char *actual_output;
if (err)
return err;
if (argc - 1 != targets->nelts)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"Passed %d target(s) to "
"svn_client_args_to_target_array() but "
"got %d back.",
argc - 1,
targets->nelts);
actual_output = APR_ARRAY_IDX(targets, 0, const char *);
if (! svn_path_is_canonical(actual_output, iterpool))
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"Input '%s' to "
"svn_client_args_to_target_array() should "
"have returned a canonical path but "
"'%s' is not.",
input,
actual_output);
if (strcmp(expected_output, actual_output) != 0)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"Input '%s' to "
"svn_client_args_to_target_array() should "
"have returned '%s' but returned '%s'.",
input,
expected_output,
actual_output);
}
else
{
if (! err)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"Unexpected success in passing '%s' "
"to svn_client_args_to_target_array().",
input);
}
}
return SVN_NO_ERROR;
}
/* A helper function for test_patch().
* It compares a patched or reject file against expected content using the
* specified EOL. It also deletes the file if the check was successful. */
static svn_error_t *
check_patch_result(const char *path, const char **expected_lines, const char *eol,
int num_expected_lines, apr_pool_t *pool)
{
svn_stream_t *stream;
apr_pool_t *iterpool;
int i;
SVN_ERR(svn_stream_open_readonly(&stream, path, pool, pool));
i = 0;
iterpool = svn_pool_create(pool);
while (TRUE)
{
svn_boolean_t eof;
svn_stringbuf_t *line;
svn_pool_clear(iterpool);
SVN_ERR(svn_stream_readline(stream, &line, eol, &eof, pool));
if (i < num_expected_lines)
if (strcmp(expected_lines[i++], line->data) != 0)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"%s line %d didn't match the expected line "
"(strlen=%d vs strlen=%d)", path, i,
(int)strlen(expected_lines[i-1]),
(int)strlen(line->data));
if (eof)
break;
}
svn_pool_destroy(iterpool);
SVN_TEST_ASSERT(i == num_expected_lines);
SVN_ERR(svn_stream_close(stream));
SVN_ERR(svn_io_remove_file2(path, FALSE, pool));
return SVN_NO_ERROR;
}
/* A baton for the patch collection function. */
struct patch_collection_baton
{
apr_hash_t *patched_tempfiles;
apr_hash_t *reject_tempfiles;
apr_pool_t *state_pool;
};
/* Collect all the patch information we're interested in. */
static svn_error_t *
patch_collection_func(void *baton,
svn_boolean_t *filtered,
const char *canon_path_from_patchfile,
const char *patch_abspath,
const char *reject_abspath,
apr_pool_t *scratch_pool)
{
struct patch_collection_baton *pcb = baton;
if (patch_abspath)
apr_hash_set(pcb->patched_tempfiles,
apr_pstrdup(pcb->state_pool, canon_path_from_patchfile),
APR_HASH_KEY_STRING,
apr_pstrdup(pcb->state_pool, patch_abspath));
if (reject_abspath)
apr_hash_set(pcb->reject_tempfiles,
apr_pstrdup(pcb->state_pool, canon_path_from_patchfile),
APR_HASH_KEY_STRING,
apr_pstrdup(pcb->state_pool, reject_abspath));
if (filtered)
*filtered = FALSE;
return SVN_NO_ERROR;
}
static svn_error_t *
test_patch(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
const char *repos_url;
const char *wc_path;
svn_opt_revision_t rev;
svn_opt_revision_t peg_rev;
svn_client_ctx_t *ctx;
apr_file_t *patch_file;
struct patch_collection_baton pcb;
const char *patch_file_path;
const char *patched_tempfile_path;
const char *reject_tempfile_path;
const char *key;
int i;
#define NL APR_EOL_STR
#define UNIDIFF_LINES 7
const char *unidiff_patch[UNIDIFF_LINES] = {
"Index: A/D/gamma" NL,
"===================================================================\n",
"--- A/D/gamma\t(revision 1)" NL,
"+++ A/D/gamma\t(working copy)" NL,
"@@ -1 +1 @@" NL,
"-This is really the file 'gamma'." NL,
"+It is really the file 'gamma'." NL
};
#define EXPECTED_GAMMA_LINES 1
const char *expected_gamma[EXPECTED_GAMMA_LINES] = {
"This is the file 'gamma'."
};
#define EXPECTED_GAMMA_REJECT_LINES 5
const char *expected_gamma_reject[EXPECTED_GAMMA_REJECT_LINES] = {
"--- A/D/gamma",
"+++ A/D/gamma",
"@@ -1,1 +1,1 @@",
"-This is really the file 'gamma'.",
"+It is really the file 'gamma'."
};
/* Create a filesystem and repository containing the Greek tree. */
SVN_ERR(create_greek_repos(&repos_url, "test-patch-repos", opts, pool));
/* Check out the HEAD revision */
/* 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_test_data_path("test-patch", pool);
SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
svn_test_add_dir_cleanup(wc_path);
wc_path = svn_dirent_join(wc_path, "test-patch-wc", pool);
SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
rev.kind = svn_opt_revision_head;
peg_rev.kind = svn_opt_revision_unspecified;
SVN_ERR(svn_client_create_context(&ctx, pool));
SVN_ERR(svn_client_checkout4(NULL, repos_url, wc_path,
&peg_rev, &rev, svn_depth_infinity,
TRUE, FALSE,
opts->wc_format_version,
opts->store_pristine,
ctx, pool));
/* Create the patch file. */
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));
for (i = 0; i < UNIDIFF_LINES; i++)
{
apr_size_t len = strlen(unidiff_patch[i]);
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(patch_file, pool));
/* Apply the patch. */
pcb.patched_tempfiles = apr_hash_make(pool);
pcb.reject_tempfiles = apr_hash_make(pool);
pcb.state_pool = pool;
SVN_ERR(svn_client_patch(patch_file_path, wc_path, FALSE, 0, FALSE,
FALSE, FALSE, patch_collection_func, &pcb,
ctx, pool));
SVN_ERR(svn_io_file_close(patch_file, pool));
SVN_TEST_ASSERT(apr_hash_count(pcb.patched_tempfiles) == 1);
key = "A/D/gamma";
patched_tempfile_path = apr_hash_get(pcb.patched_tempfiles, key,
APR_HASH_KEY_STRING);
SVN_ERR(check_patch_result(patched_tempfile_path, expected_gamma, "\n",
EXPECTED_GAMMA_LINES, pool));
SVN_TEST_ASSERT(apr_hash_count(pcb.reject_tempfiles) == 1);
key = "A/D/gamma";
reject_tempfile_path = apr_hash_get(pcb.reject_tempfiles, key,
APR_HASH_KEY_STRING);
SVN_ERR(check_patch_result(reject_tempfile_path, expected_gamma_reject,
APR_EOL_STR, EXPECTED_GAMMA_REJECT_LINES, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
test_wc_add_scenarios(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
const char *repos_url;
const char *wc_path;
svn_revnum_t committed_rev;
svn_client_ctx_t *ctx;
svn_opt_revision_t rev, peg_rev;
const char *new_dir_path;
const char *ex_file_path;
const char *ex_dir_path;
const char *ex2_dir_path;
svn_boolean_t store_pristine;
/* Create a filesystem and repository containing the Greek tree. */
SVN_ERR(create_greek_repos(&repos_url, "test-wc-add-repos", opts, pool));
committed_rev = 1;
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));
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));
/* Checkout greek tree as wc_path */
SVN_ERR(svn_client_checkout4(NULL, repos_url, wc_path, &peg_rev, &rev,
svn_depth_infinity, FALSE, FALSE,
opts->wc_format_version,
opts->store_pristine,
ctx, pool));
SVN_ERR(svn_wc__get_settings(NULL, &store_pristine, ctx->wc_ctx,
wc_path, pool));
if (!store_pristine)
return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
"Test assumes a working copy with pristine");
/* Now checkout again as wc_path/NEW */
new_dir_path = svn_dirent_join(wc_path, "NEW", pool);
SVN_ERR(svn_client_checkout4(NULL, repos_url, new_dir_path, &peg_rev, &rev,
svn_depth_infinity, FALSE, FALSE,
opts->wc_format_version,
opts->store_pristine,
ctx, pool));
ex_dir_path = svn_dirent_join(wc_path, "NEW_add", pool);
ex2_dir_path = svn_dirent_join(wc_path, "NEW_add2", pool);
SVN_ERR(svn_io_dir_make(ex_dir_path, APR_OS_DEFAULT, pool));
SVN_ERR(svn_io_dir_make(ex2_dir_path, APR_OS_DEFAULT, pool));
SVN_ERR(svn_io_open_uniquely_named(NULL, &ex_file_path, wc_path, "new_file",
NULL, svn_io_file_del_none, pool, pool));
/* Now use an access baton to do some add operations like an old client
might do */
{
svn_wc_adm_access_t *adm_access, *adm2;
svn_boolean_t locked;
SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, wc_path, TRUE, -1, NULL, NULL,
pool));
/* ### The above svn_wc_adm_open3 creates a new svn_wc__db_t
### instance. The svn_wc_add3 below doesn't work while the
### original svn_wc__db_t created by svn_client_create_context
### remains open. Closing the wc-context gets around the
### problem but is obviously a hack. */
SVN_ERR(svn_wc_context_destroy(ctx->wc_ctx));
SVN_ERR(svn_wc_context_create(&ctx->wc_ctx, NULL, pool, pool));
/* Fix up copy as add with history */
SVN_ERR(svn_wc_add3(new_dir_path, adm_access, svn_depth_infinity,
repos_url, committed_rev, NULL, NULL, NULL, NULL,
pool));
/* Verify if the paths are locked now */
SVN_ERR(svn_wc_locked(&locked, wc_path, pool));
SVN_TEST_ASSERT(locked && "wc_path locked");
SVN_ERR(svn_wc_locked(&locked, new_dir_path, pool));
SVN_TEST_ASSERT(locked && "new_path locked");
SVN_ERR(svn_wc_adm_retrieve(&adm2, adm_access, new_dir_path, pool));
SVN_TEST_ASSERT(adm2 != NULL && "available in set");
/* Add local (new) file */
SVN_ERR(svn_wc_add3(ex_file_path, adm_access, svn_depth_unknown, NULL,
SVN_INVALID_REVNUM, NULL, NULL, NULL, NULL, pool));
/* Add local (new) directory */
SVN_ERR(svn_wc_add3(ex_dir_path, adm_access, svn_depth_infinity, NULL,
SVN_INVALID_REVNUM, NULL, NULL, NULL, NULL, pool));
SVN_ERR(svn_wc_adm_retrieve(&adm2, adm_access, ex_dir_path, pool));
SVN_TEST_ASSERT(adm2 != NULL && "available in set");
/* Add empty directory with copy trail */
SVN_ERR(svn_wc_add3(ex2_dir_path, adm_access, svn_depth_infinity,
repos_url, committed_rev, NULL, NULL, NULL, NULL,
pool));
SVN_ERR(svn_wc_adm_retrieve(&adm2, adm_access, ex2_dir_path, pool));
SVN_TEST_ASSERT(adm2 != NULL && "available in set");
SVN_ERR(svn_wc_adm_close2(adm_access, pool));
}
/* Some simple status calls to verify that the paths are added */
{
svn_wc_status3_t *status;
SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, new_dir_path, pool, pool));
SVN_TEST_ASSERT(status->node_status == svn_wc_status_added
&& status->copied
&& !strcmp(status->repos_relpath, "NEW"));
SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, ex_file_path, pool, pool));
SVN_TEST_ASSERT(status->node_status == svn_wc_status_added
&& !status->copied);
SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, ex_dir_path, pool, pool));
SVN_TEST_ASSERT(status->node_status == svn_wc_status_added
&& !status->copied);
SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, ex2_dir_path, pool, pool));
SVN_TEST_ASSERT(status->node_status == svn_wc_status_added
&& status->copied);
}
/* ### Add a commit? */
return SVN_NO_ERROR;
}
/* This is for issue #3234. */
static svn_error_t *
test_copy_crash(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
apr_array_header_t *sources;
svn_opt_revision_t rev;
svn_client_copy_source_t source;
svn_client_ctx_t *ctx;
const char *dest;
const char *repos_url;
/* Create a filesystem and repository containing the Greek tree. */
SVN_ERR(create_greek_repos(&repos_url, "test-copy-crash", opts, pool));
SVN_ERR(svn_client_create_context(&ctx, pool));
rev.kind = svn_opt_revision_head;
dest = svn_path_url_add_component2(repos_url, "A/E", pool);
source.path = svn_path_url_add_component2(repos_url, "A/B", pool);
source.revision = &rev;
source.peg_revision = &rev;
sources = apr_array_make(pool, 1, sizeof(svn_client_copy_source_t *));
APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = &source;
/* This shouldn't crash. */
SVN_ERR(svn_client_copy6(sources, dest, FALSE, TRUE, FALSE, NULL, NULL, NULL,
ctx, pool));
return SVN_NO_ERROR;
}
#ifdef TEST16K_ADD
static svn_error_t *
test_16k_add(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
svn_opt_revision_t rev;
svn_client_ctx_t *ctx;
const char *repos_url;
const char *wc_path;
svn_opt_revision_t peg_rev;
apr_array_header_t *targets;
apr_pool_t *iterpool = svn_pool_create(pool);
int i;
/* Create a filesystem and repository containing the Greek tree. */
SVN_ERR(create_greek_repos(&repos_url, "test-16k-repos", opts, pool));
/* Check out the HEAD revision */
/* 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_test_data_path("test-16k", pool);
SVN_ERR(svn_io_make_dir_recursively(wc_path, pool));
svn_test_add_dir_cleanup(wc_path);
wc_path = svn_dirent_join(wc_path, "trunk", pool);
SVN_ERR(svn_io_remove_dir2(wc_path, TRUE, NULL, NULL, pool));
rev.kind = svn_opt_revision_head;
peg_rev.kind = svn_opt_revision_unspecified;
SVN_ERR(svn_client_create_context(&ctx, pool));
SVN_ERR(svn_client_checkout4(NULL, repos_url, wc_path,
&peg_rev, &rev, svn_depth_infinity,
TRUE, FALSE,
opts->wc_format_version,
opts->store_pristine,
ctx, pool));
for (i = 0; i < 16384; i++)
{
const char *path;
svn_pool_clear(iterpool);
SVN_ERR(svn_io_open_unique_file3(NULL, &path, wc_path,
svn_io_file_del_none,
iterpool, iterpool));
SVN_ERR(svn_client_add5(path, svn_depth_unknown, FALSE, FALSE, FALSE,
FALSE, ctx, iterpool));
}
targets = apr_array_make(pool, 1, sizeof(const char *));
APR_ARRAY_PUSH(targets, const char *) = wc_path;
svn_pool_clear(iterpool);
SVN_ERR(svn_client_commit5(targets, svn_depth_infinity, FALSE, FALSE, TRUE,
NULL, NULL, NULL, NULL, ctx, iterpool));
return SVN_NO_ERROR;
}
#endif
static svn_error_t *
test_youngest_common_ancestor(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
const char *repos_url;
const char *repos_uuid = "fake-uuid"; /* the functions we call don't care */
svn_client_ctx_t *ctx;
svn_opt_revision_t head_rev = { svn_opt_revision_head, { 0 } };
svn_opt_revision_t zero_rev = { svn_opt_revision_number, { 0 } };
svn_client_copy_source_t source;
apr_array_header_t *sources;
const char *dest;
svn_client__pathrev_t *yc_ancestor;
/* Create a filesystem and repository containing the Greek tree. */
SVN_ERR(create_greek_repos(&repos_url, "test-youngest-common-ancestor", opts, pool));
SVN_ERR(svn_client_create_context(&ctx, pool));
/* Copy a file into dir 'A', keeping its own basename. */
sources = apr_array_make(pool, 1, sizeof(svn_client_copy_source_t *));
source.path = svn_path_url_add_component2(repos_url, "iota", pool);
source.peg_revision = &head_rev;
source.revision = &head_rev;
APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = &source;
dest = svn_path_url_add_component2(repos_url, "A", pool);
SVN_ERR(svn_client_copy6(sources, dest, TRUE /* copy_as_child */,
FALSE /* make_parents */,
FALSE /* ignore_externals */,
NULL, NULL, NULL, ctx, pool));
/* Test: YCA(iota@2, A/iota@2) is iota@1. */
SVN_ERR(svn_client__get_youngest_common_ancestor(
&yc_ancestor,
svn_client__pathrev_create_with_relpath(
repos_url, repos_uuid, 2, "iota", pool),
svn_client__pathrev_create_with_relpath(
repos_url, repos_uuid, 2, "A/iota", pool),
NULL, ctx, pool, pool));
SVN_TEST_STRING_ASSERT(svn_client__pathrev_relpath(yc_ancestor, pool),
"iota");
SVN_TEST_ASSERT(yc_ancestor->rev == 1);
/* Copy the root directory (at revision 0) into A as 'ROOT'. */
sources = apr_array_make(pool, 1, sizeof(svn_client_copy_source_t *));
source.path = repos_url;
source.peg_revision = &zero_rev;
source.revision = &zero_rev;
APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = &source;
dest = svn_path_url_add_component2(repos_url, "A/ROOT", pool);
SVN_ERR(svn_client_copy6(sources, dest, FALSE /* copy_as_child */,
FALSE /* make_parents */,
FALSE /* ignore_externals */,
NULL, NULL, NULL, ctx, pool));
/* Test: YCA(''@0, A/ROOT@3) is ''@0 (handled as a special case). */
SVN_ERR(svn_client__get_youngest_common_ancestor(
&yc_ancestor,
svn_client__pathrev_create_with_relpath(
repos_url, repos_uuid, 0, "", pool),
svn_client__pathrev_create_with_relpath(
repos_url, repos_uuid, 3, "A/ROOT", pool),
NULL, ctx, pool, pool));
SVN_TEST_STRING_ASSERT(svn_client__pathrev_relpath(yc_ancestor, pool), "");
SVN_TEST_ASSERT(yc_ancestor->rev == 0);
return SVN_NO_ERROR;
}
static svn_error_t *
test_foreign_repos_copy(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 *repos2_url;
const char *wc_path;
svn_client_ctx_t *ctx;
svn_ra_session_t *ra_session;
svn_client__pathrev_t *loc;
/* Create a filesystem and repository containing the Greek tree. */
SVN_ERR(create_greek_repos(&repos_url, "foreign-copy1", opts, pool));
SVN_ERR(create_greek_repos(&repos2_url, "foreign-copy2", opts, pool));
wc_path = svn_test_data_path("test-foreign-repos-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);
wc_path = svn_dirent_join(wc_path, "foreign-wc", pool);
rev.kind = svn_opt_revision_head;
peg_rev.kind = svn_opt_revision_unspecified;
SVN_ERR(svn_client_create_context(&ctx, pool));
/* Checkout greek tree as wc_path */
SVN_ERR(svn_client_checkout4(NULL, repos_url, wc_path, &peg_rev, &rev,
svn_depth_infinity,
FALSE, FALSE,
opts->wc_format_version,
opts->store_pristine,
ctx, pool));
SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
repos2_url, NULL, &peg_rev, &rev,
ctx, pool));
loc->url = svn_path_url_add_component2(repos2_url, "A", pool);
SVN_WC__CALL_WITH_WRITE_LOCK(
svn_client__repos_to_wc_copy_by_editor(NULL /*sleep*/, svn_node_dir,
loc->url, loc->rev,
svn_dirent_join(wc_path, "A-copied", pool),
ra_session, ctx, pool),
ctx->wc_ctx, wc_path, FALSE, pool);
SVN_ERR(svn_ra_reparent(ra_session, repos2_url, pool));
loc->url = svn_path_url_add_component2(repos2_url, "iota", pool);
SVN_WC__CALL_WITH_WRITE_LOCK(
svn_client__repos_to_wc_copy_by_editor(NULL /*sleep*/, svn_node_file,
loc->url, loc->rev,
svn_dirent_join(wc_path, "iota-copied", pool),
ra_session, ctx, pool),
ctx->wc_ctx, wc_path, FALSE, pool);
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 filesystem 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_checkout4(NULL,
svn_path_url_add_component2(repos_url, "AA", pool),
wc_path,
&head_rev, &head_rev, svn_depth_empty,
FALSE, FALSE,
opts->wc_format_version,
opts->store_pristine,
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 filesystem 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_checkout4(NULL,
apr_pstrcat(pool, repos_url, "/A", SVN_VA_NULL),
wc_path, &rev, &rev, svn_depth_immediates,
FALSE, FALSE,
opts->wc_format_version,
opts->store_pristine,
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)
svn_sort__array(results, 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 filesystem 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 filesystem 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;
}
/* ========================================================================== */
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_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_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