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_pools.h"
31 #include "svn_dirent_uri.h"
36 #include "svn_private_config.h"
37 #include "private/svn_fspath.h"
38 #include "private/svn_wc_private.h"
42 svn_client_info2_dup(const svn_client_info2_t *info,
45 svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
48 new_info->URL = apr_pstrdup(pool, info->URL);
49 if (new_info->repos_root_URL)
50 new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
51 if (new_info->repos_UUID)
52 new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
53 if (info->last_changed_author)
54 new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author);
56 new_info->lock = svn_lock_dup(info->lock, pool);
57 if (new_info->wc_info)
58 new_info->wc_info = svn_wc_info_dup(info->wc_info, pool);
62 /* Set *INFO to a new info struct built from DIRENT
63 and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
64 Pointer fields are copied by reference, not dup'd. */
66 build_info_from_dirent(svn_client_info2_t **info,
67 const svn_dirent_t *dirent,
69 const svn_client__pathrev_t *pathrev,
72 svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
74 tmpinfo->URL = pathrev->url;
75 tmpinfo->rev = pathrev->rev;
76 tmpinfo->kind = dirent->kind;
77 tmpinfo->repos_UUID = pathrev->repos_uuid;
78 tmpinfo->repos_root_URL = pathrev->repos_root_url;
79 tmpinfo->last_changed_rev = dirent->created_rev;
80 tmpinfo->last_changed_date = dirent->time;
81 tmpinfo->last_changed_author = dirent->last_author;
83 tmpinfo->size = dirent->size;
85 tmpinfo->wc_info = NULL;
92 /* The dirent fields we care about for our calls to svn_ra_get_dir2. */
93 #define DIRENT_FIELDS (SVN_DIRENT_KIND | \
94 SVN_DIRENT_CREATED_REV | \
96 SVN_DIRENT_LAST_AUTHOR)
99 /* Helper func for recursively fetching svn_dirent_t's from a remote
100 directory and pushing them at an info-receiver callback.
102 DEPTH is the depth starting at DIR, even though RECEIVER is never
103 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
104 RECEIVER on all children of DIR, but none of their children; if
105 svn_depth_files, then invoke RECEIVER on file children of DIR but
106 not on subdirectories; if svn_depth_infinity, recurse fully.
107 DIR is a relpath, relative to the root of RA_SESSION.
110 push_dir_info(svn_ra_session_t *ra_session,
111 const svn_client__pathrev_t *pathrev,
113 svn_client_info_receiver2_t receiver,
114 void *receiver_baton,
116 svn_client_ctx_t *ctx,
120 apr_hash_t *tmpdirents;
121 apr_hash_index_t *hi;
122 apr_pool_t *subpool = svn_pool_create(pool);
124 SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
125 dir, pathrev->rev, DIRENT_FIELDS, pool));
127 for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
129 const char *path, *fs_path;
131 svn_client_info2_t *info;
132 const char *name = svn__apr_hash_index_key(hi);
133 svn_dirent_t *the_ent = svn__apr_hash_index_val(hi);
134 svn_client__pathrev_t *child_pathrev;
136 svn_pool_clear(subpool);
138 if (ctx->cancel_func)
139 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
141 path = svn_relpath_join(dir, name, subpool);
142 child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool);
143 fs_path = svn_client__pathrev_fspath(child_pathrev, subpool);
145 lock = svn_hash_gets(locks, fs_path);
147 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
150 if (depth >= svn_depth_immediates
151 || (depth == svn_depth_files && the_ent->kind == svn_node_file))
153 SVN_ERR(receiver(receiver_baton, path, info, subpool));
156 if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
158 SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
159 receiver, receiver_baton,
160 depth, ctx, locks, subpool));
164 svn_pool_destroy(subpool);
170 /* Set *SAME_P to TRUE if URL exists in the head of the repository and
171 refers to the same resource as it does in REV, using POOL for
172 temporary allocations. RA_SESSION is an open RA session for URL. */
174 same_resource_in_head(svn_boolean_t *same_p,
177 svn_ra_session_t *ra_session,
178 svn_client_ctx_t *ctx,
182 svn_opt_revision_t start_rev, peg_rev;
183 const char *head_url;
185 start_rev.kind = svn_opt_revision_head;
186 peg_rev.kind = svn_opt_revision_number;
187 peg_rev.value.number = rev;
189 err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
195 ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
196 (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
198 svn_error_clear(err);
205 /* ### Currently, the URLs should always be equal, since we can't
206 ### walk forwards in history. */
207 *same_p = (strcmp(url, head_url) == 0);
212 /* A baton for wc_info_receiver(), containing the wrapped receiver. */
213 typedef struct wc_info_receiver_baton_t
215 svn_client_info_receiver2_t client_receiver_func;
216 void *client_receiver_baton;
217 } wc_info_receiver_baton_t;
219 /* A receiver for WC info, implementing svn_client_info_receiver2_t.
220 * Convert the WC info to client info and pass it to the client info
221 * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */
223 wc_info_receiver(void *baton,
224 const char *abspath_or_url,
225 const svn_wc__info2_t *wc_info,
226 apr_pool_t *scratch_pool)
228 wc_info_receiver_baton_t *b = baton;
229 svn_client_info2_t client_info;
231 /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */
232 client_info.repos_root_URL = wc_info->repos_root_URL;
233 client_info.repos_UUID = wc_info->repos_UUID;
234 client_info.rev = wc_info->rev;
235 client_info.URL = wc_info->URL;
237 client_info.kind = wc_info->kind;
238 client_info.size = wc_info->size;
239 client_info.last_changed_rev = wc_info->last_changed_rev;
240 client_info.last_changed_date = wc_info->last_changed_date;
241 client_info.last_changed_author = wc_info->last_changed_author;
243 client_info.lock = wc_info->lock;
245 client_info.wc_info = wc_info->wc_info;
247 return b->client_receiver_func(b->client_receiver_baton,
248 abspath_or_url, &client_info, scratch_pool);
252 svn_client_info3(const char *abspath_or_url,
253 const svn_opt_revision_t *peg_revision,
254 const svn_opt_revision_t *revision,
256 svn_boolean_t fetch_excluded,
257 svn_boolean_t fetch_actual_only,
258 const apr_array_header_t *changelists,
259 svn_client_info_receiver2_t receiver,
260 void *receiver_baton,
261 svn_client_ctx_t *ctx,
264 svn_ra_session_t *ra_session;
265 svn_client__pathrev_t *pathrev;
267 svn_boolean_t related;
268 const char *base_name;
269 svn_dirent_t *the_ent;
270 svn_client_info2_t *info;
273 if (depth == svn_depth_unknown)
274 depth = svn_depth_empty;
276 if ((revision == NULL
277 || revision->kind == svn_opt_revision_unspecified)
278 && (peg_revision == NULL
279 || peg_revision->kind == svn_opt_revision_unspecified))
281 /* Do all digging in the working copy. */
282 wc_info_receiver_baton_t b;
284 b.client_receiver_func = receiver;
285 b.client_receiver_baton = receiver_baton;
286 return svn_error_trace(
287 svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth,
288 fetch_excluded, fetch_actual_only, changelists,
289 wc_info_receiver, &b,
290 ctx->cancel_func, ctx->cancel_baton, pool));
293 /* Go repository digging instead. */
295 /* Trace rename history (starting at path_or_url@peg_revision) and
296 return RA session to the possibly-renamed URL as it exists in REVISION.
297 The ra_session returned will be anchored on this "final" URL. */
298 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
299 abspath_or_url, NULL, peg_revision,
300 revision, ctx, pool));
302 svn_uri_split(NULL, &base_name, pathrev->url, pool);
304 /* Get the dirent for the URL itself. */
305 SVN_ERR(svn_client__ra_stat_compatible(ra_session, pathrev->rev, &the_ent,
306 DIRENT_FIELDS, ctx, pool));
309 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
310 _("URL '%s' non-existent in revision %ld"),
311 pathrev->url, pathrev->rev);
313 /* Check if the URL exists in HEAD and refers to the same resource.
314 In this case, we check the repository for a lock on this URL.
316 ### There is a possible race here, since HEAD might have changed since
317 ### we checked it. A solution to this problem could be to do the below
318 ### check in a loop which only terminates if the HEAD revision is the same
319 ### before and after this check. That could, however, lead to a
320 ### starvation situation instead. */
321 SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev,
322 ra_session, ctx, pool));
325 err = svn_ra_get_lock(ra_session, &lock, "", pool);
327 /* An old mod_dav_svn will always work; there's nothing wrong with
328 doing a PROPFIND for a property named "DAV:supportedlock". But
329 an old svnserve will error. */
330 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
332 svn_error_clear(err);
336 return svn_error_trace(err);
341 /* Push the URL's dirent (and lock) at the callback.*/
342 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool));
343 SVN_ERR(receiver(receiver_baton, base_name, info, pool));
345 /* Possibly recurse, using the original RA session. */
346 if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
350 if (peg_revision->kind == svn_opt_revision_head)
352 err = svn_ra_get_locks2(ra_session, &locks, "", depth,
355 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
357 (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED
358 || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
360 svn_error_clear(err);
361 locks = apr_hash_make(pool); /* use an empty hash */
364 return svn_error_trace(err);
367 locks = apr_hash_make(pool); /* use an empty hash */
369 SVN_ERR(push_dir_info(ra_session, pathrev, "",
370 receiver, receiver_baton,
371 depth, ctx, locks, pool));
379 svn_client_get_wc_root(const char **wcroot_abspath,
380 const char *local_abspath,
381 svn_client_ctx_t *ctx,
382 apr_pool_t *result_pool,
383 apr_pool_t *scratch_pool)
385 return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
386 result_pool, scratch_pool);
390 /* NOTE: This function was requested by the TortoiseSVN project. See
393 svn_client_min_max_revisions(svn_revnum_t *min_revision,
394 svn_revnum_t *max_revision,
395 const char *local_abspath,
396 svn_boolean_t committed,
397 svn_client_ctx_t *ctx,
398 apr_pool_t *scratch_pool)
400 return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
401 local_abspath, committed, scratch_pool);