/*
* list.c: list local and remote directory entries.
*
* ====================================================================
* Copyright (c) 2000-2006 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
/* ==================================================================== */
#include "client.h"
#include "svn_client.h"
#include "svn_path.h"
#include "svn_pools.h"
#include "svn_time.h"
#include "svn_sorts.h"
#include "svn_private_config.h"
/* Get the directory entries of DIR at REV (relative to the root of
RA_SESSION), getting at least the fields specified by DIRENT_FIELDS.
Use the cancelation function/baton of CTX to check for cancellation.
If RECURSE is TRUE, recurse into child directories.
LOCKS, if non-NULL, is a hash mapping const char * paths to svn_lock_t
objects and FS_PATH is the absolute filesystem path of the RA session.
Use POOL for temporary allocations.
*/
static svn_error_t *
get_dir_contents(apr_uint32_t dirent_fields,
const char *dir,
svn_revnum_t rev,
svn_ra_session_t *ra_session,
apr_hash_t *locks,
const char *fs_path,
svn_boolean_t recurse,
svn_client_ctx_t *ctx,
svn_client_list_func_t list_func,
void *baton,
apr_pool_t *pool)
{
apr_hash_t *tmpdirents;
apr_pool_t *subpool = svn_pool_create(pool);
apr_array_header_t *array;
int i;
/* Get the directory's entries, but not its props. */
SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
dir, rev, dirent_fields, pool));
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
/* Sort the hash, so we can call the callback in a "deterministic" order. */
array = svn_sort__hash(tmpdirents, svn_sort_compare_items_lexically, pool);
for (i = 0; i < array->nelts; ++i)
{
svn_sort__item_t *item = &APR_ARRAY_IDX(array, i, svn_sort__item_t);
const char *path;
svn_dirent_t *the_ent = apr_hash_get(tmpdirents, item->key, item->klen);
svn_lock_t *lock;
svn_pool_clear(subpool);
path = svn_path_join(dir, item->key, subpool);
if (locks)
{
const char *abs_path = svn_path_join(fs_path, path, subpool);
lock = apr_hash_get(locks, abs_path, APR_HASH_KEY_STRING);
}
else
lock = NULL;
SVN_ERR(list_func(baton, path, the_ent, lock, fs_path, subpool));
if (recurse && the_ent->kind == svn_node_dir)
SVN_ERR(get_dir_contents(dirent_fields, path, rev,
ra_session, locks, fs_path, recurse, ctx,
list_func, baton, subpool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_client_list(const char *path_or_url,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *revision,
svn_boolean_t recurse,
apr_uint32_t dirent_fields,
svn_boolean_t fetch_locks,
svn_client_list_func_t list_func,
void *baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_ra_session_t *ra_session;
svn_revnum_t rev;
svn_dirent_t *dirent;
const char *url;
const char *repos_root;
const char *fs_path;
svn_error_t *err;
apr_hash_t *locks;
/* We use the kind field to determine if we should recurse, so we
always need it. */
dirent_fields |= SVN_DIRENT_KIND;
/* Get an RA plugin for this filesystem object. */
SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev,
&url, path_or_url, peg_revision,
revision, ctx, pool));
SVN_ERR(svn_ra_get_repos_root(ra_session, &repos_root, pool));
/* Get path relative to repository root. */
fs_path = svn_path_is_child(repos_root, url, pool);
/* Make sure fs_path begins with a slash. fs_path is NULL if the url is
the repository root. */
fs_path = svn_path_join("/", fs_path ? fs_path : "", pool);
fs_path = svn_path_uri_decode(fs_path, pool);
err = svn_ra_stat(ra_session, "", rev, &dirent, pool);
/* svnserve before 1.2 doesn't support the above, so fall back on
a less efficient method. */
if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
{
svn_node_kind_t kind;
svn_error_clear(err);
SVN_ERR(svn_ra_check_path(ra_session, "", rev, &kind, pool));
if (kind != svn_node_none)
{
if (strcmp(url, repos_root) != 0)
{
svn_ra_session_t *parent_session;
apr_hash_t *parent_ents;
const char *parent_url, *base_name;
/* Open another session to the path's parent. This server
doesn't support svn_ra_reparent anyway, so don't try it. */
svn_path_split(url, &parent_url, &base_name, pool);
/* 'base_name' is now the last component of an URL, but we want
to use it as a plain file name. Therefore, we must URI-decode
it. */
base_name = svn_path_uri_decode(base_name, pool);
SVN_ERR(svn_client__open_ra_session_internal(&parent_session,
parent_url, NULL,
NULL, NULL, FALSE,
TRUE, ctx, pool));
/* Get all parent's entries, no props. */
SVN_ERR(svn_ra_get_dir2(parent_session, &parent_ents, NULL,
NULL, "", rev, dirent_fields, pool));
/* Get the relevant entry. */
dirent = apr_hash_get(parent_ents, base_name,
APR_HASH_KEY_STRING);
}
else
{
/* We can't get the directory entry for the repository root,
but we can still get the information we want.
The created-rev of the repository root must, by definition,
be rev. */
dirent = apr_palloc(pool, sizeof(*dirent));
dirent->kind = kind;
dirent->size = 0;
if (dirent_fields & SVN_DIRENT_HAS_PROPS)
{
apr_hash_t *props;
SVN_ERR(svn_ra_get_dir2(ra_session, NULL, NULL, &props,
"", rev, 0 /* no dirent fields */,
pool));
dirent->has_props = (apr_hash_count(props) != 0);
}
dirent->created_rev = rev;
if (dirent_fields & (SVN_DIRENT_TIME | SVN_DIRENT_LAST_AUTHOR))
{
apr_hash_t *props;
svn_string_t *val;
SVN_ERR(svn_ra_rev_proplist(ra_session, rev, &props,
pool));
val = apr_hash_get(props, SVN_PROP_REVISION_DATE,
APR_HASH_KEY_STRING);
if (val)
SVN_ERR(svn_time_from_cstring(&dirent->time, val->data,
pool));
else
dirent->time = 0;
val = apr_hash_get(props, SVN_PROP_REVISION_AUTHOR,
APR_HASH_KEY_STRING);
dirent->last_author = val ? val->data : NULL;
}
}
}
else
dirent = NULL;
}
else if (err)
return err;
if (! dirent)
return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
_("URL '%s' non-existent in that revision"),
url);
/* Maybe get all locks under url. */
if (fetch_locks)
{
/* IMPORTANT: If locks are stored in a more temporary pool, we need
to fix store_dirent below to duplicate the locks. */
err = svn_ra_get_locks(ra_session, &locks, "", pool);
if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
{
svn_error_clear(err);
locks = NULL;
}
else if (err)
return err;
}
else
locks = NULL;
/* Report the dirent for the target. */
SVN_ERR(list_func(baton, "", dirent, locks
? (apr_hash_get(locks, fs_path,
APR_HASH_KEY_STRING))
: NULL, fs_path, pool));
if (dirent->kind == svn_node_dir)
SVN_ERR(get_dir_contents(dirent_fields, "", rev, ra_session, locks,
fs_path, recurse, ctx, list_func, baton, pool));
return SVN_NO_ERROR;
}
/* Baton used by compatibility wrapper svn_client_ls3. */
struct ls_baton {
apr_hash_t *dirents;
apr_hash_t *locks;
apr_pool_t *pool;
};
/* This implements svn_client_list_func_t. */
static svn_error_t *
store_dirent(void *baton, const char *path, const svn_dirent_t *dirent,
const svn_lock_t *lock, const char *abs_path, apr_pool_t *pool)
{
struct ls_baton *lb = baton;
/* The dirent is allocated in a temporary pool, so duplicate it into the
correct pool. Note, however, that the locks are stored in the correct
pool already. */
dirent = svn_dirent_dup(dirent, lb->pool);
/* An empty path means we are called for the target of the operation.
For compatibility, we only store the target if it is a file, and we
store it under the basename of the URL. Note that this makes it
impossible to differentiate between the target being a directory with a
child with the same basename as the target and the target being a file,
but that's how it was implemented. */
if (path[0] == '\0')
{
if (dirent->kind == svn_node_file)
{
const char *base_name = svn_path_basename(abs_path, lb->pool);
apr_hash_set(lb->dirents, base_name, APR_HASH_KEY_STRING, dirent);
if (lock)
apr_hash_set(lb->locks, base_name, APR_HASH_KEY_STRING, lock);
}
}
else
{
path = apr_pstrdup(lb->pool, path);
apr_hash_set(lb->dirents, path, APR_HASH_KEY_STRING, dirent);
if (lock)
apr_hash_set(lb->locks, path, APR_HASH_KEY_STRING, lock);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_client_ls3(apr_hash_t **dirents,
apr_hash_t **locks,
const char *path_or_url,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *revision,
svn_boolean_t recurse,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
struct ls_baton lb;
*dirents = lb.dirents = apr_hash_make(pool);
if (locks)
*locks = lb.locks = apr_hash_make(pool);
lb.pool = pool;
return svn_client_list(path_or_url, peg_revision, revision, recurse,
SVN_DIRENT_ALL, locks != NULL,
store_dirent, &lb, ctx, pool);
}
svn_error_t *
svn_client_ls2(apr_hash_t **dirents,
const char *path_or_url,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *revision,
svn_boolean_t recurse,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_ls3(dirents, NULL, path_or_url, peg_revision,
revision, recurse, ctx, pool);
}
svn_error_t *
svn_client_ls(apr_hash_t **dirents,
const char *path_or_url,
svn_opt_revision_t *revision,
svn_boolean_t recurse,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
return svn_client_ls2(dirents, path_or_url, revision,
revision, recurse, ctx, pool);
}
syntax highlighted by Code2HTML, v. 0.9.1