#include <apr_pools.h>
#include <assert.h>
#include "svn_error.h"
#include "svn_pools.h"
#include "svn_string.h"
#include "svn_sorts.h"
#include "svn_ra.h"
#include "svn_wc.h"
#include "svn_client.h"
#include "svn_path.h"
#include "svn_props.h"
#include "client.h"
#include "svn_private_config.h"
static svn_error_t *
open_admin_tmp_file(apr_file_t **fp,
void *callback_baton,
apr_pool_t *pool)
{
svn_client__callback_baton_t *cb = callback_baton;
SVN_ERR(svn_wc_create_tmp_file2(fp, NULL, cb->base_dir,
svn_io_file_del_on_close, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
open_tmp_file(apr_file_t **fp,
void *callback_baton,
apr_pool_t *pool)
{
svn_client__callback_baton_t *cb = callback_baton;
const char *truepath;
if (cb->base_dir && ! cb->read_only_wc)
truepath = apr_pstrdup(pool, cb->base_dir);
else
SVN_ERR(svn_io_temp_dir(&truepath, pool));
truepath = svn_path_join(truepath, "tempfile", pool);
SVN_ERR(svn_io_open_unique_file2(fp, NULL, truepath, ".tmp",
svn_io_file_del_on_close, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
get_wc_prop(void *baton,
const char *relpath,
const char *name,
const svn_string_t **value,
apr_pool_t *pool)
{
svn_client__callback_baton_t *cb = baton;
*value = NULL;
if (cb->commit_items)
{
int i;
for (i = 0; i < cb->commit_items->nelts; i++)
{
svn_client_commit_item2_t *item
= APR_ARRAY_IDX(cb->commit_items, i,
svn_client_commit_item2_t *);
if (! strcmp(relpath,
svn_path_uri_decode(item->url, pool)))
return svn_wc_prop_get(value, name, item->path, cb->base_access,
pool);
}
return SVN_NO_ERROR;
}
else if (cb->base_dir == NULL)
return SVN_NO_ERROR;
return svn_wc_prop_get(value, name,
svn_path_join(cb->base_dir, relpath, pool),
cb->base_access, pool);
}
static svn_error_t *
push_wc_prop(void *baton,
const char *relpath,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
svn_client__callback_baton_t *cb = baton;
int i;
if (! cb->commit_items)
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Attempt to set wc property '%s' on '%s' in a non-commit operation"),
name, svn_path_local_style(relpath, pool));
for (i = 0; i < cb->commit_items->nelts; i++)
{
svn_client_commit_item2_t *item
= APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item2_t *);
if (strcmp(relpath, svn_path_uri_decode(item->url, pool)) == 0)
{
apr_pool_t *cpool = item->wcprop_changes->pool;
svn_prop_t *prop = apr_palloc(cpool, sizeof(*prop));
prop->name = apr_pstrdup(cpool, name);
if (value)
{
prop->value
= svn_string_ncreate(value->data, value->len, cpool);
}
else
prop->value = NULL;
*((svn_prop_t **) apr_array_push(item->wcprop_changes)) = prop;
return SVN_NO_ERROR;
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
set_wc_prop(void *baton,
const char *path,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
svn_client__callback_baton_t *cb = baton;
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
const char *full_path = svn_path_join(cb->base_dir, path, pool);
SVN_ERR(svn_wc_entry(&entry, full_path, cb->base_access, FALSE, pool));
if (! entry)
return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
svn_path_local_style(full_path, pool));
SVN_ERR(svn_wc_adm_retrieve(&adm_access, cb->base_access,
(entry->kind == svn_node_dir
? full_path
: svn_path_dirname(full_path, pool)),
pool));
return svn_wc_prop_set2(name, value, full_path, adm_access, TRUE, pool);
}
struct invalidate_wcprop_walk_baton
{
const char *prop_name;
svn_wc_adm_access_t *base_access;
};
static svn_error_t *
invalidate_wcprop_for_entry(const char *path,
const svn_wc_entry_t *entry,
void *walk_baton,
apr_pool_t *pool)
{
struct invalidate_wcprop_walk_baton *wb = walk_baton;
svn_wc_adm_access_t *entry_access;
SVN_ERR(svn_wc_adm_retrieve(&entry_access, wb->base_access,
((entry->kind == svn_node_dir)
? path
: svn_path_dirname(path, pool)),
pool));
return svn_wc_prop_set2(wb->prop_name, NULL, path, entry_access,
FALSE, pool);
}
static svn_error_t *
invalidate_wc_props(void *baton,
const char *path,
const char *prop_name,
apr_pool_t *pool)
{
svn_client__callback_baton_t *cb = baton;
svn_wc_entry_callbacks_t walk_callbacks;
struct invalidate_wcprop_walk_baton wb;
svn_wc_adm_access_t *adm_access;
wb.base_access = cb->base_access;
wb.prop_name = prop_name;
walk_callbacks.found_entry = invalidate_wcprop_for_entry;
path = svn_path_join(cb->base_dir, path, pool);
SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, cb->base_access, path,
pool));
SVN_ERR(svn_wc_walk_entries2(path, adm_access, &walk_callbacks, &wb,
FALSE, cb->ctx->cancel_func,
cb->ctx->cancel_baton, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
const char *base_url,
const char *base_dir,
svn_wc_adm_access_t *base_access,
apr_array_header_t *commit_items,
svn_boolean_t use_admin,
svn_boolean_t read_only_wc,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_callbacks2_t *cbtable = apr_pcalloc(pool, sizeof(*cbtable));
svn_client__callback_baton_t *cb = apr_pcalloc(pool, sizeof(*cb));
cbtable->open_tmp_file = use_admin ? open_admin_tmp_file : open_tmp_file;
cbtable->get_wc_prop = use_admin ? get_wc_prop : NULL;
cbtable->set_wc_prop = read_only_wc ? NULL : set_wc_prop;
cbtable->push_wc_prop = commit_items ? push_wc_prop : NULL;
cbtable->invalidate_wc_props = read_only_wc ? NULL : invalidate_wc_props;
cbtable->auth_baton = ctx->auth_baton;
cbtable->progress_func = ctx->progress_func;
cbtable->progress_baton = ctx->progress_baton;
cb->base_dir = base_dir;
cb->base_access = base_access;
cb->read_only_wc = read_only_wc;
cb->pool = pool;
cb->commit_items = commit_items;
cb->ctx = ctx;
SVN_ERR(svn_ra_open2(ra_session, base_url, cbtable, cb,
ctx->config, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_client_open_ra_session(svn_ra_session_t **session,
const char *url,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client__open_ra_session_internal(session, url, NULL, NULL, NULL,
FALSE, TRUE, ctx, pool);
}
svn_error_t *
svn_client_uuid_from_url(const char **uuid,
const char *url,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_session_t *ra_session;
apr_pool_t *subpool = svn_pool_create(pool);
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
NULL,
NULL, NULL, FALSE, TRUE,
ctx, subpool));
SVN_ERR(svn_ra_get_uuid(ra_session, uuid, subpool));
*uuid = apr_pstrdup(pool, *uuid);
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_client_uuid_from_path(const char **uuid,
const char *path,
svn_wc_adm_access_t *adm_access,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, path, adm_access,
TRUE, pool));
if (! entry)
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("Can't find entry for '%s'"),
svn_path_local_style(path, pool));
if (entry->uuid)
{
*uuid = entry->uuid;
}
else if (entry->url)
{
SVN_ERR(svn_client_uuid_from_url(uuid, entry->url, ctx, pool));
}
else
{
svn_boolean_t is_root;
SVN_ERR(svn_wc_is_wc_root(&is_root, path, adm_access, pool));
if (is_root)
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' has no URL"),
svn_path_local_style(path, pool));
else
return svn_client_uuid_from_path(uuid, svn_path_dirname(path, pool),
adm_access, ctx, pool);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__prev_log_path(const char **prev_path_p,
char *action_p,
svn_revnum_t *copyfrom_rev_p,
apr_hash_t *changed_paths,
const char *path,
svn_node_kind_t kind,
svn_revnum_t revision,
apr_pool_t *pool)
{
svn_log_changed_path_t *change;
const char *prev_path = NULL;
assert(path);
if (action_p)
*action_p = 'M';
if (copyfrom_rev_p)
*copyfrom_rev_p = SVN_INVALID_REVNUM;
change = apr_hash_get(changed_paths, path, APR_HASH_KEY_STRING);
if (change)
{
if (change->action != 'A' && change->action != 'R')
{
prev_path = path;
}
else
{
if (change->copyfrom_path)
prev_path = apr_pstrdup(pool, change->copyfrom_path);
else
prev_path = NULL;
*prev_path_p = prev_path;
if (action_p)
*action_p = change->action;
if (copyfrom_rev_p)
*copyfrom_rev_p = change->copyfrom_rev;
return SVN_NO_ERROR;
}
}
if (apr_hash_count(changed_paths))
{
int i;
apr_array_header_t *paths;
paths = svn_sort__hash(changed_paths,
svn_sort_compare_items_as_paths, pool);
for (i = paths->nelts; i > 0; i--)
{
svn_sort__item_t item = APR_ARRAY_IDX(paths,
i - 1, svn_sort__item_t);
const char *ch_path = item.key;
int len = strlen(ch_path);
if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
continue;
change = apr_hash_get(changed_paths, ch_path, len);
if (change->copyfrom_path)
{
if (action_p)
*action_p = change->action;
if (copyfrom_rev_p)
*copyfrom_rev_p = change->copyfrom_rev;
prev_path = svn_path_join(change->copyfrom_path,
path + len + 1, pool);
break;
}
}
}
if (! prev_path)
{
if (kind == svn_node_dir)
prev_path = apr_pstrdup(pool, path);
else
return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
_("Missing changed-path information for "
"'%s' in revision %ld"),
svn_path_local_style(path, pool), revision);
}
*prev_path_p = prev_path;
return SVN_NO_ERROR;
}
struct log_receiver_baton
{
svn_node_kind_t kind;
const char *last_path;
svn_revnum_t start_revision;
const char **start_path_p;
svn_revnum_t end_revision;
const char **end_path_p;
svn_revnum_t peg_revision;
const char *peg_path;
svn_client_ctx_t *ctx;
apr_pool_t *pool;
};
static svn_error_t *
log_receiver(void *baton,
apr_hash_t *changed_paths,
svn_revnum_t revision,
const char *author,
const char *date,
const char *message,
apr_pool_t *pool)
{
struct log_receiver_baton *lrb = baton;
const char *current_path = lrb->last_path;
const char *prev_path;
if (lrb->ctx->cancel_func)
SVN_ERR(lrb->ctx->cancel_func(lrb->ctx->cancel_baton));
if (!changed_paths)
return SVN_NO_ERROR;
if (! current_path)
return SVN_NO_ERROR;
if ((! *lrb->start_path_p) && (revision <= lrb->start_revision))
*lrb->start_path_p = apr_pstrdup(lrb->pool, current_path);
if ((! *lrb->end_path_p) && (revision <= lrb->end_revision))
*lrb->end_path_p = apr_pstrdup(lrb->pool, current_path);
if ((! lrb->peg_path) && (revision <= lrb->peg_revision))
lrb->peg_path = apr_pstrdup(lrb->pool, current_path);
SVN_ERR(svn_client__prev_log_path(&prev_path, NULL, NULL, changed_paths,
current_path, lrb->kind,
revision, pool));
if (! prev_path)
lrb->last_path = NULL;
else if (strcmp(prev_path, current_path) != 0)
lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
return SVN_NO_ERROR;
}
static svn_error_t *
slow_locations(const char **start_path, const char** end_path,
const char *abs_path, svn_revnum_t peg_revnum,
svn_revnum_t start_revnum, svn_revnum_t end_revnum,
const char *orig_path,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct log_receiver_baton lrb = { 0 };
apr_array_header_t *targets;
svn_revnum_t youngest, oldest;
svn_boolean_t pegrev_is_youngest = FALSE;
SVN_ERR(svn_ra_check_path(ra_session, "", peg_revnum, &(lrb.kind), pool));
if (lrb.kind == svn_node_none)
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, NULL,
_("path '%s' doesn't exist in revision %ld"),
orig_path, peg_revnum);
lrb.last_path = abs_path;
lrb.start_revision = start_revnum;
lrb.end_revision = end_revnum;
lrb.peg_revision = peg_revnum;
lrb.start_path_p = start_path;
lrb.end_path_p = end_path;
lrb.ctx = ctx;
lrb.pool = pool;
youngest = peg_revnum;
youngest = (start_revnum > youngest) ? start_revnum : youngest;
youngest = (end_revnum > youngest) ? end_revnum : youngest;
oldest = peg_revnum;
oldest = (start_revnum < oldest) ? start_revnum : oldest;
oldest = (end_revnum < oldest) ? end_revnum : oldest;
targets = apr_array_make(pool, 1, sizeof(const char *));
APR_ARRAY_PUSH(targets, const char *) = "";
SVN_ERR(svn_ra_get_log(ra_session, targets, youngest, oldest, 0,
TRUE, FALSE, log_receiver, &lrb, pool));
if (! lrb.peg_path)
lrb.peg_path = lrb.last_path;
if (! *start_path)
*start_path = lrb.last_path;
if (! *end_path)
*end_path = lrb.last_path;
if (! lrb.peg_path)
return svn_error_createf
(APR_EGENERAL, NULL,
_("Unable to find repository location for '%s' in revision %ld"),
orig_path, peg_revnum);
if (! pegrev_is_youngest)
{
if (strcmp(abs_path, lrb.peg_path) != 0)
return svn_error_createf
(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
_("'%s' in revision %ld is an unrelated object"),
orig_path, youngest);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__repos_locations(const char **start_url,
svn_opt_revision_t **start_revision,
const char **end_url,
svn_opt_revision_t **end_revision,
svn_ra_session_t *ra_session,
const char *path,
const svn_opt_revision_t *revision,
const svn_opt_revision_t *start,
const svn_opt_revision_t *end,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
const char *repos_url;
const char *url;
const char *start_path = NULL;
const char *end_path = NULL;
svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
svn_revnum_t start_revnum, end_revnum;
apr_array_header_t *revs;
apr_hash_t *rev_locs;
apr_pool_t *subpool = svn_pool_create(pool);
svn_error_t *err;
if (revision->kind == svn_opt_revision_unspecified
|| start->kind == svn_opt_revision_unspecified)
return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
if (! svn_path_is_url(path))
{
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path,
FALSE, 0, ctx->cancel_func,
ctx->cancel_baton, pool));
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc_adm_close(adm_access));
if (entry->copyfrom_url && revision->kind == svn_opt_revision_working)
{
url = entry->copyfrom_url;
peg_revnum = entry->copyfrom_rev;
if (!entry->url || strcmp(entry->url, entry->copyfrom_url) != 0)
{
ra_session = NULL;
}
}
else if (entry->url)
{
url = entry->url;
}
else
{
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' has no URL"),
svn_path_local_style(path, pool));
}
}
else
{
url = path;
}
if (! ra_session)
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url, NULL,
NULL, NULL, FALSE, TRUE,
ctx, subpool));
if (peg_revnum == SVN_INVALID_REVNUM)
SVN_ERR(svn_client__get_revision_number(&peg_revnum,
ra_session, revision, path,
pool));
SVN_ERR(svn_client__get_revision_number(&start_revnum,
ra_session, start, path, pool));
if (end->kind == svn_opt_revision_unspecified)
end_revnum = start_revnum;
else
SVN_ERR(svn_client__get_revision_number(&end_revnum,
ra_session, end, path, pool));
*start_revision = apr_pcalloc(pool, sizeof(**start_revision));
(*start_revision)->kind = svn_opt_revision_number;
(*start_revision)->value.number = start_revnum;
if (end->kind != svn_opt_revision_unspecified)
{
*end_revision = apr_pcalloc(pool, sizeof(**end_revision));
(*end_revision)->kind = svn_opt_revision_number;
(*end_revision)->value.number = end_revnum;
}
if (start_revnum == peg_revnum && end_revnum == peg_revnum)
{
*start_url = url;
if (end->kind != svn_opt_revision_unspecified)
*end_url = url;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
SVN_ERR(svn_ra_get_repos_root(ra_session, &repos_url, subpool));
revs = apr_array_make(subpool, 2, sizeof(svn_revnum_t));
APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
if (end_revnum != start_revnum)
APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
if (! (err = svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
revs, subpool)))
{
start_path = apr_hash_get(rev_locs, &start_revnum,
sizeof(svn_revnum_t));
end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
}
else if (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
{
svn_error_clear(err);
SVN_ERR(slow_locations(&start_path, &end_path,
svn_path_uri_decode(url + strlen(repos_url),
subpool),
peg_revnum, start_revnum, end_revnum,
path, ra_session, ctx, subpool));
}
else
return err;
if (! start_path)
return svn_error_createf
(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
_("Unable to find repository location for '%s' in revision %ld"),
path, start_revnum);
if (! end_path)
return svn_error_createf
(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
_("The location for '%s' for revision %ld does not exist in the "
"repository or refers to an unrelated object"),
path, end_revnum);
if (start_path[0] == '/')
start_path = start_path + 1;
if (end_path[0] == '/')
end_path = end_path + 1;
*start_url = svn_path_join(repos_url, svn_path_uri_encode(start_path,
pool), pool);
if (end->kind != svn_opt_revision_unspecified)
*end_url = svn_path_join(repos_url, svn_path_uri_encode(end_path,
pool), pool);
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__ra_session_from_path(svn_ra_session_t **ra_session_p,
svn_revnum_t *rev_p,
const char **url_p,
const char *path_or_url,
const svn_opt_revision_t *peg_revision_p,
const svn_opt_revision_t *revision,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_session_t *ra_session;
const char *initial_url, *url;
const svn_opt_revision_t *good_rev;
svn_opt_revision_t peg_revision, start_rev;
svn_opt_revision_t dead_end_rev;
svn_opt_revision_t *ignored_rev, *new_rev;
svn_revnum_t rev;
const char *ignored_url;
SVN_ERR(svn_client_url_from_path(&initial_url, path_or_url, pool));
if (! initial_url)
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' has no URL"), path_or_url);
if (revision->kind == svn_opt_revision_unspecified &&
peg_revision_p->kind != svn_opt_revision_unspecified)
revision = peg_revision_p;
if (svn_path_is_url(path_or_url))
{
if (revision->kind == svn_opt_revision_unspecified)
start_rev.kind = svn_opt_revision_head;
else
start_rev = *revision;
if (peg_revision_p->kind == svn_opt_revision_unspecified)
peg_revision.kind = svn_opt_revision_head;
else
peg_revision = *peg_revision_p;
}
else
{
if (revision->kind == svn_opt_revision_unspecified)
start_rev.kind = svn_opt_revision_base;
else
start_rev = *revision;
if (peg_revision_p->kind == svn_opt_revision_unspecified)
peg_revision.kind = svn_opt_revision_working;
else
peg_revision = *peg_revision_p;
}
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, initial_url,
NULL, NULL, NULL,
FALSE, FALSE, ctx, pool));
dead_end_rev.kind = svn_opt_revision_unspecified;
SVN_ERR(svn_client__repos_locations(&url, &new_rev,
&ignored_url, &ignored_rev,
ra_session,
path_or_url, &peg_revision,
&start_rev, &dead_end_rev,
ctx, pool));
good_rev = new_rev;
SVN_ERR(svn_ra_reparent(ra_session, url, pool));
SVN_ERR(svn_client__get_revision_number(&rev, ra_session,
good_rev, url, pool));
if (! SVN_IS_VALID_REVNUM(rev))
SVN_ERR(svn_ra_get_latest_revnum(ra_session, &rev, pool));
*ra_session_p = ra_session;
*rev_p = rev;
*url_p = url;
return SVN_NO_ERROR;
}