2 * info.c: return system-generated metadata about paths or URLs.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
29 #include "svn_client.h"
30 #include "svn_dirent_uri.h"
32 #include "svn_pools.h"
33 #include "svn_sorts.h"
37 #include "svn_private_config.h"
38 #include "private/svn_fspath.h"
39 #include "private/svn_sorts_private.h"
40 #include "private/svn_wc_private.h"
44 svn_client_info2_dup(const svn_client_info2_t *info,
47 svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
50 new_info->URL = apr_pstrdup(pool, info->URL);
51 if (new_info->repos_root_URL)
52 new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
53 if (new_info->repos_UUID)
54 new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
55 if (info->last_changed_author)
56 new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author);
58 new_info->lock = svn_lock_dup(info->lock, pool);
59 if (new_info->wc_info)
60 new_info->wc_info = svn_wc_info_dup(info->wc_info, pool);
64 /* Handle externals for svn_client_info4() */
67 do_external_info(apr_hash_t *external_map,
69 svn_boolean_t fetch_excluded,
70 svn_boolean_t fetch_actual_only,
71 const apr_array_header_t *changelists,
72 svn_client_info_receiver2_t receiver,
74 svn_client_ctx_t *ctx,
75 apr_pool_t *scratch_pool)
77 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
78 apr_array_header_t *externals;
81 externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically,
84 /* Loop over the hash of new values (we don't care about the old
85 ones). This is a mapping of versioned directories to property
87 for (i = 0; i < externals->nelts; i++)
89 svn_node_kind_t external_kind;
90 svn_sort__item_t item = APR_ARRAY_IDX(externals, i, svn_sort__item_t);
91 const char *local_abspath = item.key;
92 const char *defining_abspath = item.value;
93 svn_opt_revision_t opt_rev;
96 svn_pool_clear(iterpool);
98 /* Obtain information on the expected external. */
99 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL,
100 &opt_rev.value.number,
101 ctx->wc_ctx, defining_abspath,
102 local_abspath, FALSE,
103 iterpool, iterpool));
105 if (external_kind != svn_node_dir)
108 SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool));
109 if (kind != svn_node_dir)
112 /* Tell the client we're starting an external info. */
113 if (ctx->notify_func2)
116 svn_wc_create_notify(local_abspath,
117 svn_wc_notify_info_external,
118 iterpool), iterpool);
120 SVN_ERR(svn_client_info4(local_abspath,
121 NULL /* peg_revision */,
126 TRUE /* include_externals */,
128 receiver, receiver_baton,
132 svn_pool_destroy(iterpool);
136 /* Set *INFO to a new info struct built from DIRENT
137 and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
138 Pointer fields are copied by reference, not dup'd. */
140 build_info_from_dirent(svn_client_info2_t **info,
141 const svn_dirent_t *dirent,
143 const svn_client__pathrev_t *pathrev,
146 svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
148 tmpinfo->URL = pathrev->url;
149 tmpinfo->rev = pathrev->rev;
150 tmpinfo->kind = dirent->kind;
151 tmpinfo->repos_UUID = pathrev->repos_uuid;
152 tmpinfo->repos_root_URL = pathrev->repos_root_url;
153 tmpinfo->last_changed_rev = dirent->created_rev;
154 tmpinfo->last_changed_date = dirent->time;
155 tmpinfo->last_changed_author = dirent->last_author;
156 tmpinfo->lock = lock;
157 tmpinfo->size = dirent->size;
159 tmpinfo->wc_info = NULL;
166 /* The dirent fields we care about for our calls to svn_ra_get_dir2. */
167 #define DIRENT_FIELDS (SVN_DIRENT_KIND | \
168 SVN_DIRENT_CREATED_REV | \
170 SVN_DIRENT_LAST_AUTHOR)
173 /* Helper func for recursively fetching svn_dirent_t's from a remote
174 directory and pushing them at an info-receiver callback.
176 DEPTH is the depth starting at DIR, even though RECEIVER is never
177 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
178 RECEIVER on all children of DIR, but none of their children; if
179 svn_depth_files, then invoke RECEIVER on file children of DIR but
180 not on subdirectories; if svn_depth_infinity, recurse fully.
181 DIR is a relpath, relative to the root of RA_SESSION.
184 push_dir_info(svn_ra_session_t *ra_session,
185 const svn_client__pathrev_t *pathrev,
187 svn_client_info_receiver2_t receiver,
188 void *receiver_baton,
190 svn_client_ctx_t *ctx,
194 apr_hash_t *tmpdirents;
195 apr_hash_index_t *hi;
196 apr_pool_t *subpool = svn_pool_create(pool);
198 SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
199 dir, pathrev->rev, DIRENT_FIELDS, pool));
201 for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
203 const char *path, *fs_path;
205 svn_client_info2_t *info;
206 const char *name = apr_hash_this_key(hi);
207 svn_dirent_t *the_ent = apr_hash_this_val(hi);
208 svn_client__pathrev_t *child_pathrev;
210 svn_pool_clear(subpool);
212 if (ctx->cancel_func)
213 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
215 path = svn_relpath_join(dir, name, subpool);
216 child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool);
217 fs_path = svn_client__pathrev_fspath(child_pathrev, subpool);
219 lock = svn_hash_gets(locks, fs_path);
221 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
224 if (depth >= svn_depth_immediates
225 || (depth == svn_depth_files && the_ent->kind == svn_node_file))
227 SVN_ERR(receiver(receiver_baton, path, info, subpool));
230 if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
232 SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
233 receiver, receiver_baton,
234 depth, ctx, locks, subpool));
238 svn_pool_destroy(subpool);
244 /* Set *SAME_P to TRUE if URL exists in the head of the repository and
245 refers to the same resource as it does in REV, using POOL for
246 temporary allocations. RA_SESSION is an open RA session for URL. */
248 same_resource_in_head(svn_boolean_t *same_p,
251 svn_ra_session_t *ra_session,
252 svn_client_ctx_t *ctx,
256 svn_opt_revision_t start_rev, peg_rev;
257 const char *head_url;
259 start_rev.kind = svn_opt_revision_head;
260 peg_rev.kind = svn_opt_revision_number;
261 peg_rev.value.number = rev;
263 err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
269 ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
270 (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
272 svn_error_clear(err);
279 /* ### Currently, the URLs should always be equal, since we can't
280 ### walk forwards in history. */
281 *same_p = (strcmp(url, head_url) == 0);
286 /* A baton for wc_info_receiver(), containing the wrapped receiver. */
287 typedef struct wc_info_receiver_baton_t
289 svn_client_info_receiver2_t client_receiver_func;
290 void *client_receiver_baton;
291 } wc_info_receiver_baton_t;
293 /* A receiver for WC info, implementing svn_client_info_receiver2_t.
294 * Convert the WC info to client info and pass it to the client info
295 * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */
297 wc_info_receiver(void *baton,
298 const char *abspath_or_url,
299 const svn_wc__info2_t *wc_info,
300 apr_pool_t *scratch_pool)
302 wc_info_receiver_baton_t *b = baton;
303 svn_client_info2_t client_info;
305 /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */
306 client_info.repos_root_URL = wc_info->repos_root_URL;
307 client_info.repos_UUID = wc_info->repos_UUID;
308 client_info.rev = wc_info->rev;
309 client_info.URL = wc_info->URL;
311 client_info.kind = wc_info->kind;
312 client_info.size = wc_info->size;
313 client_info.last_changed_rev = wc_info->last_changed_rev;
314 client_info.last_changed_date = wc_info->last_changed_date;
315 client_info.last_changed_author = wc_info->last_changed_author;
317 client_info.lock = wc_info->lock;
319 client_info.wc_info = wc_info->wc_info;
321 return b->client_receiver_func(b->client_receiver_baton,
322 abspath_or_url, &client_info, scratch_pool);
326 svn_client_info4(const char *abspath_or_url,
327 const svn_opt_revision_t *peg_revision,
328 const svn_opt_revision_t *revision,
330 svn_boolean_t fetch_excluded,
331 svn_boolean_t fetch_actual_only,
332 svn_boolean_t include_externals,
333 const apr_array_header_t *changelists,
334 svn_client_info_receiver2_t receiver,
335 void *receiver_baton,
336 svn_client_ctx_t *ctx,
339 svn_ra_session_t *ra_session;
340 svn_client__pathrev_t *pathrev;
342 svn_boolean_t related;
343 const char *base_name;
344 svn_dirent_t *the_ent;
345 svn_client_info2_t *info;
348 if (depth == svn_depth_unknown)
349 depth = svn_depth_empty;
351 if ((revision == NULL
352 || revision->kind == svn_opt_revision_unspecified)
353 && (peg_revision == NULL
354 || peg_revision->kind == svn_opt_revision_unspecified))
356 /* Do all digging in the working copy. */
357 wc_info_receiver_baton_t b;
359 b.client_receiver_func = receiver;
360 b.client_receiver_baton = receiver_baton;
361 SVN_ERR(svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth,
362 fetch_excluded, fetch_actual_only, changelists,
363 wc_info_receiver, &b,
364 ctx->cancel_func, ctx->cancel_baton, pool));
366 if (include_externals && SVN_DEPTH_IS_RECURSIVE(depth))
368 apr_hash_t *external_map;
370 SVN_ERR(svn_wc__externals_defined_below(&external_map,
371 ctx->wc_ctx, abspath_or_url,
374 SVN_ERR(do_external_info(external_map,
375 depth, fetch_excluded, fetch_actual_only,
377 receiver, receiver_baton, ctx, pool));
383 /* Go repository digging instead. */
385 /* Trace rename history (starting at path_or_url@peg_revision) and
386 return RA session to the possibly-renamed URL as it exists in REVISION.
387 The ra_session returned will be anchored on this "final" URL. */
388 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
389 abspath_or_url, NULL, peg_revision,
390 revision, ctx, pool));
391 base_name = svn_uri_basename(pathrev->url, pool);
393 /* Get the dirent for the URL itself. */
394 SVN_ERR(svn_ra_stat(ra_session, "", pathrev->rev, &the_ent, pool));
397 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
398 _("URL '%s' non-existent in revision %ld"),
399 pathrev->url, pathrev->rev);
401 /* Check if the URL exists in HEAD and refers to the same resource.
402 In this case, we check the repository for a lock on this URL.
404 ### There is a possible race here, since HEAD might have changed since
405 ### we checked it. A solution to this problem could be to do the below
406 ### check in a loop which only terminates if the HEAD revision is the same
407 ### before and after this check. That could, however, lead to a
408 ### starvation situation instead. */
409 SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev,
410 ra_session, ctx, pool));
413 err = svn_ra_get_lock(ra_session, &lock, "", pool);
415 /* An old mod_dav_svn will always work; there's nothing wrong with
416 doing a PROPFIND for a property named "DAV:supportedlock". But
417 an old svnserve will error. */
418 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
420 svn_error_clear(err);
424 return svn_error_trace(err);
429 /* Push the URL's dirent (and lock) at the callback.*/
430 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool));
431 SVN_ERR(receiver(receiver_baton, base_name, info, pool));
433 /* Possibly recurse, using the original RA session. */
434 if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
438 if (peg_revision->kind == svn_opt_revision_head)
440 err = svn_ra_get_locks2(ra_session, &locks, "", depth,
443 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
444 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
446 svn_error_clear(err);
447 locks = apr_hash_make(pool); /* use an empty hash */
450 return svn_error_trace(err);
453 locks = apr_hash_make(pool); /* use an empty hash */
455 SVN_ERR(push_dir_info(ra_session, pathrev, "",
456 receiver, receiver_baton,
457 depth, ctx, locks, pool));
465 svn_client_get_wc_root(const char **wcroot_abspath,
466 const char *local_abspath,
467 svn_client_ctx_t *ctx,
468 apr_pool_t *result_pool,
469 apr_pool_t *scratch_pool)
471 return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
472 result_pool, scratch_pool);
476 /* NOTE: This function was requested by the TortoiseSVN project. See
479 svn_client_min_max_revisions(svn_revnum_t *min_revision,
480 svn_revnum_t *max_revision,
481 const char *local_abspath,
482 svn_boolean_t committed,
483 svn_client_ctx_t *ctx,
484 apr_pool_t *scratch_pool)
486 return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
487 local_abspath, committed, scratch_pool);