1251881Speter/* 2251881Speter * info.c: return system-generated metadata about paths or URLs. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter#include "client.h" 29251881Speter#include "svn_client.h" 30251881Speter#include "svn_pools.h" 31251881Speter#include "svn_dirent_uri.h" 32251881Speter#include "svn_path.h" 33251881Speter#include "svn_hash.h" 34251881Speter#include "svn_wc.h" 35251881Speter 36251881Speter#include "svn_private_config.h" 37251881Speter#include "private/svn_fspath.h" 38251881Speter#include "private/svn_wc_private.h" 39251881Speter 40251881Speter 41251881Spetersvn_client_info2_t * 42251881Spetersvn_client_info2_dup(const svn_client_info2_t *info, 43251881Speter apr_pool_t *pool) 44251881Speter{ 45251881Speter svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info)); 46251881Speter 47251881Speter if (new_info->URL) 48251881Speter new_info->URL = apr_pstrdup(pool, info->URL); 49251881Speter if (new_info->repos_root_URL) 50251881Speter new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL); 51251881Speter if (new_info->repos_UUID) 52251881Speter new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID); 53251881Speter if (info->last_changed_author) 54251881Speter new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author); 55251881Speter if (new_info->lock) 56251881Speter new_info->lock = svn_lock_dup(info->lock, pool); 57251881Speter if (new_info->wc_info) 58251881Speter new_info->wc_info = svn_wc_info_dup(info->wc_info, pool); 59251881Speter return new_info; 60251881Speter} 61251881Speter 62251881Speter/* Set *INFO to a new info struct built from DIRENT 63251881Speter and (possibly NULL) svn_lock_t LOCK, all allocated in POOL. 64251881Speter Pointer fields are copied by reference, not dup'd. */ 65251881Speterstatic svn_error_t * 66251881Speterbuild_info_from_dirent(svn_client_info2_t **info, 67251881Speter const svn_dirent_t *dirent, 68251881Speter svn_lock_t *lock, 69251881Speter const svn_client__pathrev_t *pathrev, 70251881Speter apr_pool_t *pool) 71251881Speter{ 72251881Speter svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo)); 73251881Speter 74251881Speter tmpinfo->URL = pathrev->url; 75251881Speter tmpinfo->rev = pathrev->rev; 76251881Speter tmpinfo->kind = dirent->kind; 77251881Speter tmpinfo->repos_UUID = pathrev->repos_uuid; 78251881Speter tmpinfo->repos_root_URL = pathrev->repos_root_url; 79251881Speter tmpinfo->last_changed_rev = dirent->created_rev; 80251881Speter tmpinfo->last_changed_date = dirent->time; 81251881Speter tmpinfo->last_changed_author = dirent->last_author; 82251881Speter tmpinfo->lock = lock; 83251881Speter tmpinfo->size = dirent->size; 84251881Speter 85251881Speter tmpinfo->wc_info = NULL; 86251881Speter 87251881Speter *info = tmpinfo; 88251881Speter return SVN_NO_ERROR; 89251881Speter} 90251881Speter 91251881Speter 92251881Speter/* The dirent fields we care about for our calls to svn_ra_get_dir2. */ 93251881Speter#define DIRENT_FIELDS (SVN_DIRENT_KIND | \ 94251881Speter SVN_DIRENT_CREATED_REV | \ 95251881Speter SVN_DIRENT_TIME | \ 96251881Speter SVN_DIRENT_LAST_AUTHOR) 97251881Speter 98251881Speter 99251881Speter/* Helper func for recursively fetching svn_dirent_t's from a remote 100251881Speter directory and pushing them at an info-receiver callback. 101251881Speter 102251881Speter DEPTH is the depth starting at DIR, even though RECEIVER is never 103251881Speter invoked on DIR: if DEPTH is svn_depth_immediates, then invoke 104251881Speter RECEIVER on all children of DIR, but none of their children; if 105251881Speter svn_depth_files, then invoke RECEIVER on file children of DIR but 106251881Speter not on subdirectories; if svn_depth_infinity, recurse fully. 107251881Speter DIR is a relpath, relative to the root of RA_SESSION. 108251881Speter*/ 109251881Speterstatic svn_error_t * 110251881Speterpush_dir_info(svn_ra_session_t *ra_session, 111251881Speter const svn_client__pathrev_t *pathrev, 112251881Speter const char *dir, 113251881Speter svn_client_info_receiver2_t receiver, 114251881Speter void *receiver_baton, 115251881Speter svn_depth_t depth, 116251881Speter svn_client_ctx_t *ctx, 117251881Speter apr_hash_t *locks, 118251881Speter apr_pool_t *pool) 119251881Speter{ 120251881Speter apr_hash_t *tmpdirents; 121251881Speter apr_hash_index_t *hi; 122251881Speter apr_pool_t *subpool = svn_pool_create(pool); 123251881Speter 124251881Speter SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL, 125251881Speter dir, pathrev->rev, DIRENT_FIELDS, pool)); 126251881Speter 127251881Speter for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi)) 128251881Speter { 129251881Speter const char *path, *fs_path; 130251881Speter svn_lock_t *lock; 131251881Speter svn_client_info2_t *info; 132251881Speter const char *name = svn__apr_hash_index_key(hi); 133251881Speter svn_dirent_t *the_ent = svn__apr_hash_index_val(hi); 134251881Speter svn_client__pathrev_t *child_pathrev; 135251881Speter 136251881Speter svn_pool_clear(subpool); 137251881Speter 138251881Speter if (ctx->cancel_func) 139251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 140251881Speter 141251881Speter path = svn_relpath_join(dir, name, subpool); 142251881Speter child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool); 143251881Speter fs_path = svn_client__pathrev_fspath(child_pathrev, subpool); 144251881Speter 145251881Speter lock = svn_hash_gets(locks, fs_path); 146251881Speter 147251881Speter SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev, 148251881Speter subpool)); 149251881Speter 150251881Speter if (depth >= svn_depth_immediates 151251881Speter || (depth == svn_depth_files && the_ent->kind == svn_node_file)) 152251881Speter { 153251881Speter SVN_ERR(receiver(receiver_baton, path, info, subpool)); 154251881Speter } 155251881Speter 156251881Speter if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir) 157251881Speter { 158251881Speter SVN_ERR(push_dir_info(ra_session, child_pathrev, path, 159251881Speter receiver, receiver_baton, 160251881Speter depth, ctx, locks, subpool)); 161251881Speter } 162251881Speter } 163251881Speter 164251881Speter svn_pool_destroy(subpool); 165251881Speter 166251881Speter return SVN_NO_ERROR; 167251881Speter} 168251881Speter 169251881Speter 170251881Speter/* Set *SAME_P to TRUE if URL exists in the head of the repository and 171251881Speter refers to the same resource as it does in REV, using POOL for 172251881Speter temporary allocations. RA_SESSION is an open RA session for URL. */ 173251881Speterstatic svn_error_t * 174251881Spetersame_resource_in_head(svn_boolean_t *same_p, 175251881Speter const char *url, 176251881Speter svn_revnum_t rev, 177251881Speter svn_ra_session_t *ra_session, 178251881Speter svn_client_ctx_t *ctx, 179251881Speter apr_pool_t *pool) 180251881Speter{ 181251881Speter svn_error_t *err; 182251881Speter svn_opt_revision_t start_rev, peg_rev; 183251881Speter const char *head_url; 184251881Speter 185251881Speter start_rev.kind = svn_opt_revision_head; 186251881Speter peg_rev.kind = svn_opt_revision_number; 187251881Speter peg_rev.value.number = rev; 188251881Speter 189251881Speter err = svn_client__repos_locations(&head_url, NULL, NULL, NULL, 190251881Speter ra_session, 191251881Speter url, &peg_rev, 192251881Speter &start_rev, NULL, 193251881Speter ctx, pool); 194251881Speter if (err && 195251881Speter ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) || 196251881Speter (err->apr_err == SVN_ERR_FS_NOT_FOUND))) 197251881Speter { 198251881Speter svn_error_clear(err); 199251881Speter *same_p = FALSE; 200251881Speter return SVN_NO_ERROR; 201251881Speter } 202251881Speter else 203251881Speter SVN_ERR(err); 204251881Speter 205251881Speter /* ### Currently, the URLs should always be equal, since we can't 206251881Speter ### walk forwards in history. */ 207251881Speter *same_p = (strcmp(url, head_url) == 0); 208251881Speter 209251881Speter return SVN_NO_ERROR; 210251881Speter} 211251881Speter 212251881Speter/* A baton for wc_info_receiver(), containing the wrapped receiver. */ 213251881Spetertypedef struct wc_info_receiver_baton_t 214251881Speter{ 215251881Speter svn_client_info_receiver2_t client_receiver_func; 216251881Speter void *client_receiver_baton; 217251881Speter} wc_info_receiver_baton_t; 218251881Speter 219251881Speter/* A receiver for WC info, implementing svn_client_info_receiver2_t. 220251881Speter * Convert the WC info to client info and pass it to the client info 221251881Speter * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */ 222251881Speterstatic svn_error_t * 223251881Speterwc_info_receiver(void *baton, 224251881Speter const char *abspath_or_url, 225251881Speter const svn_wc__info2_t *wc_info, 226251881Speter apr_pool_t *scratch_pool) 227251881Speter{ 228251881Speter wc_info_receiver_baton_t *b = baton; 229251881Speter svn_client_info2_t client_info; 230251881Speter 231251881Speter /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */ 232251881Speter client_info.repos_root_URL = wc_info->repos_root_URL; 233251881Speter client_info.repos_UUID = wc_info->repos_UUID; 234251881Speter client_info.rev = wc_info->rev; 235251881Speter client_info.URL = wc_info->URL; 236251881Speter 237251881Speter client_info.kind = wc_info->kind; 238251881Speter client_info.size = wc_info->size; 239251881Speter client_info.last_changed_rev = wc_info->last_changed_rev; 240251881Speter client_info.last_changed_date = wc_info->last_changed_date; 241251881Speter client_info.last_changed_author = wc_info->last_changed_author; 242251881Speter 243251881Speter client_info.lock = wc_info->lock; 244251881Speter 245251881Speter client_info.wc_info = wc_info->wc_info; 246251881Speter 247251881Speter return b->client_receiver_func(b->client_receiver_baton, 248251881Speter abspath_or_url, &client_info, scratch_pool); 249251881Speter} 250251881Speter 251251881Spetersvn_error_t * 252251881Spetersvn_client_info3(const char *abspath_or_url, 253251881Speter const svn_opt_revision_t *peg_revision, 254251881Speter const svn_opt_revision_t *revision, 255251881Speter svn_depth_t depth, 256251881Speter svn_boolean_t fetch_excluded, 257251881Speter svn_boolean_t fetch_actual_only, 258251881Speter const apr_array_header_t *changelists, 259251881Speter svn_client_info_receiver2_t receiver, 260251881Speter void *receiver_baton, 261251881Speter svn_client_ctx_t *ctx, 262251881Speter apr_pool_t *pool) 263251881Speter{ 264251881Speter svn_ra_session_t *ra_session; 265251881Speter svn_client__pathrev_t *pathrev; 266251881Speter svn_lock_t *lock; 267251881Speter svn_boolean_t related; 268251881Speter const char *base_name; 269251881Speter svn_dirent_t *the_ent; 270251881Speter svn_client_info2_t *info; 271251881Speter svn_error_t *err; 272251881Speter 273251881Speter if (depth == svn_depth_unknown) 274251881Speter depth = svn_depth_empty; 275251881Speter 276251881Speter if ((revision == NULL 277251881Speter || revision->kind == svn_opt_revision_unspecified) 278251881Speter && (peg_revision == NULL 279251881Speter || peg_revision->kind == svn_opt_revision_unspecified)) 280251881Speter { 281251881Speter /* Do all digging in the working copy. */ 282251881Speter wc_info_receiver_baton_t b; 283251881Speter 284251881Speter b.client_receiver_func = receiver; 285251881Speter b.client_receiver_baton = receiver_baton; 286251881Speter return svn_error_trace( 287251881Speter svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth, 288251881Speter fetch_excluded, fetch_actual_only, changelists, 289251881Speter wc_info_receiver, &b, 290251881Speter ctx->cancel_func, ctx->cancel_baton, pool)); 291251881Speter } 292251881Speter 293251881Speter /* Go repository digging instead. */ 294251881Speter 295251881Speter /* Trace rename history (starting at path_or_url@peg_revision) and 296251881Speter return RA session to the possibly-renamed URL as it exists in REVISION. 297251881Speter The ra_session returned will be anchored on this "final" URL. */ 298251881Speter SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev, 299251881Speter abspath_or_url, NULL, peg_revision, 300251881Speter revision, ctx, pool)); 301251881Speter 302251881Speter svn_uri_split(NULL, &base_name, pathrev->url, pool); 303251881Speter 304251881Speter /* Get the dirent for the URL itself. */ 305251881Speter SVN_ERR(svn_client__ra_stat_compatible(ra_session, pathrev->rev, &the_ent, 306251881Speter DIRENT_FIELDS, ctx, pool)); 307251881Speter 308251881Speter if (! the_ent) 309251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 310251881Speter _("URL '%s' non-existent in revision %ld"), 311251881Speter pathrev->url, pathrev->rev); 312251881Speter 313251881Speter /* Check if the URL exists in HEAD and refers to the same resource. 314251881Speter In this case, we check the repository for a lock on this URL. 315251881Speter 316251881Speter ### There is a possible race here, since HEAD might have changed since 317251881Speter ### we checked it. A solution to this problem could be to do the below 318251881Speter ### check in a loop which only terminates if the HEAD revision is the same 319251881Speter ### before and after this check. That could, however, lead to a 320251881Speter ### starvation situation instead. */ 321251881Speter SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev, 322251881Speter ra_session, ctx, pool)); 323251881Speter if (related) 324251881Speter { 325251881Speter err = svn_ra_get_lock(ra_session, &lock, "", pool); 326251881Speter 327251881Speter /* An old mod_dav_svn will always work; there's nothing wrong with 328251881Speter doing a PROPFIND for a property named "DAV:supportedlock". But 329251881Speter an old svnserve will error. */ 330251881Speter if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) 331251881Speter { 332251881Speter svn_error_clear(err); 333251881Speter lock = NULL; 334251881Speter } 335251881Speter else if (err) 336251881Speter return svn_error_trace(err); 337251881Speter } 338251881Speter else 339251881Speter lock = NULL; 340251881Speter 341251881Speter /* Push the URL's dirent (and lock) at the callback.*/ 342251881Speter SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool)); 343251881Speter SVN_ERR(receiver(receiver_baton, base_name, info, pool)); 344251881Speter 345251881Speter /* Possibly recurse, using the original RA session. */ 346251881Speter if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir)) 347251881Speter { 348251881Speter apr_hash_t *locks; 349251881Speter 350251881Speter if (peg_revision->kind == svn_opt_revision_head) 351251881Speter { 352251881Speter err = svn_ra_get_locks2(ra_session, &locks, "", depth, 353251881Speter pool); 354251881Speter 355251881Speter /* Catch specific errors thrown by old mod_dav_svn or svnserve. */ 356251881Speter if (err && 357251881Speter (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED 358251881Speter || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)) 359251881Speter { 360251881Speter svn_error_clear(err); 361251881Speter locks = apr_hash_make(pool); /* use an empty hash */ 362251881Speter } 363251881Speter else if (err) 364251881Speter return svn_error_trace(err); 365251881Speter } 366251881Speter else 367251881Speter locks = apr_hash_make(pool); /* use an empty hash */ 368251881Speter 369251881Speter SVN_ERR(push_dir_info(ra_session, pathrev, "", 370251881Speter receiver, receiver_baton, 371251881Speter depth, ctx, locks, pool)); 372251881Speter } 373251881Speter 374251881Speter return SVN_NO_ERROR; 375251881Speter} 376251881Speter 377251881Speter 378251881Spetersvn_error_t * 379251881Spetersvn_client_get_wc_root(const char **wcroot_abspath, 380251881Speter const char *local_abspath, 381251881Speter svn_client_ctx_t *ctx, 382251881Speter apr_pool_t *result_pool, 383251881Speter apr_pool_t *scratch_pool) 384251881Speter{ 385251881Speter return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath, 386251881Speter result_pool, scratch_pool); 387251881Speter} 388251881Speter 389251881Speter 390251881Speter/* NOTE: This function was requested by the TortoiseSVN project. See 391251881Speter issue #3927. */ 392251881Spetersvn_error_t * 393251881Spetersvn_client_min_max_revisions(svn_revnum_t *min_revision, 394251881Speter svn_revnum_t *max_revision, 395251881Speter const char *local_abspath, 396251881Speter svn_boolean_t committed, 397251881Speter svn_client_ctx_t *ctx, 398251881Speter apr_pool_t *scratch_pool) 399251881Speter{ 400251881Speter return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx, 401251881Speter local_abspath, committed, scratch_pool); 402251881Speter} 403