1 /*
2 * info.c: return system-generated metadata about paths or URLs.
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 #include "client.h"
29 #include "svn_client.h"
30 #include "svn_dirent_uri.h"
31 #include "svn_hash.h"
32 #include "svn_pools.h"
33 #include "svn_sorts.h"
34
35 #include "svn_wc.h"
36
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"
41
42
43 svn_client_info2_t *
svn_client_info2_dup(const svn_client_info2_t * info,apr_pool_t * pool)44 svn_client_info2_dup(const svn_client_info2_t *info,
45 apr_pool_t *pool)
46 {
47 svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
48
49 if (new_info->URL)
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);
57 if (new_info->lock)
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);
61 return new_info;
62 }
63
64 /* Handle externals for svn_client_info4() */
65
66 static svn_error_t *
do_external_info(apr_hash_t * external_map,svn_depth_t depth,svn_boolean_t fetch_excluded,svn_boolean_t fetch_actual_only,const apr_array_header_t * changelists,svn_client_info_receiver2_t receiver,void * receiver_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)67 do_external_info(apr_hash_t *external_map,
68 svn_depth_t depth,
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,
73 void *receiver_baton,
74 svn_client_ctx_t *ctx,
75 apr_pool_t *scratch_pool)
76 {
77 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
78 apr_array_header_t *externals;
79 int i;
80
81 externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically,
82 scratch_pool);
83
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
86 values. */
87 for (i = 0; i < externals->nelts; i++)
88 {
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;
94 svn_node_kind_t kind;
95
96 svn_pool_clear(iterpool);
97
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));
104
105 if (external_kind != svn_node_dir)
106 continue;
107
108 SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool));
109 if (kind != svn_node_dir)
110 continue;
111
112 /* Tell the client we're starting an external info. */
113 if (ctx->notify_func2)
114 ctx->notify_func2(
115 ctx->notify_baton2,
116 svn_wc_create_notify(local_abspath,
117 svn_wc_notify_info_external,
118 iterpool), iterpool);
119
120 SVN_ERR(svn_client_info4(local_abspath,
121 NULL /* peg_revision */,
122 NULL /* revision */,
123 depth,
124 fetch_excluded,
125 fetch_actual_only,
126 TRUE /* include_externals */,
127 changelists,
128 receiver, receiver_baton,
129 ctx, iterpool));
130 }
131
132 svn_pool_destroy(iterpool);
133 return SVN_NO_ERROR;
134 }
135
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. */
139 static svn_error_t *
build_info_from_dirent(svn_client_info2_t ** info,const svn_dirent_t * dirent,svn_lock_t * lock,const svn_client__pathrev_t * pathrev,apr_pool_t * pool)140 build_info_from_dirent(svn_client_info2_t **info,
141 const svn_dirent_t *dirent,
142 svn_lock_t *lock,
143 const svn_client__pathrev_t *pathrev,
144 apr_pool_t *pool)
145 {
146 svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
147
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;
158
159 tmpinfo->wc_info = NULL;
160
161 *info = tmpinfo;
162 return SVN_NO_ERROR;
163 }
164
165
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 | \
169 SVN_DIRENT_TIME | \
170 SVN_DIRENT_LAST_AUTHOR | \
171 SVN_DIRENT_SIZE)
172
173
174 /* Helper func for recursively fetching svn_dirent_t's from a remote
175 directory and pushing them at an info-receiver callback.
176
177 DEPTH is the depth starting at DIR, even though RECEIVER is never
178 invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
179 RECEIVER on all children of DIR, but none of their children; if
180 svn_depth_files, then invoke RECEIVER on file children of DIR but
181 not on subdirectories; if svn_depth_infinity, recurse fully.
182 DIR is a relpath, relative to the root of RA_SESSION.
183 */
184 static svn_error_t *
push_dir_info(svn_ra_session_t * ra_session,const svn_client__pathrev_t * pathrev,const char * dir,svn_client_info_receiver2_t receiver,void * receiver_baton,svn_depth_t depth,svn_client_ctx_t * ctx,apr_hash_t * locks,apr_pool_t * pool)185 push_dir_info(svn_ra_session_t *ra_session,
186 const svn_client__pathrev_t *pathrev,
187 const char *dir,
188 svn_client_info_receiver2_t receiver,
189 void *receiver_baton,
190 svn_depth_t depth,
191 svn_client_ctx_t *ctx,
192 apr_hash_t *locks,
193 apr_pool_t *pool)
194 {
195 apr_hash_t *tmpdirents;
196 apr_hash_index_t *hi;
197 apr_pool_t *subpool = svn_pool_create(pool);
198
199 SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
200 dir, pathrev->rev, DIRENT_FIELDS, pool));
201
202 for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
203 {
204 const char *path, *fs_path;
205 svn_lock_t *lock;
206 svn_client_info2_t *info;
207 const char *name = apr_hash_this_key(hi);
208 svn_dirent_t *the_ent = apr_hash_this_val(hi);
209 svn_client__pathrev_t *child_pathrev;
210
211 svn_pool_clear(subpool);
212
213 if (ctx->cancel_func)
214 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
215
216 path = svn_relpath_join(dir, name, subpool);
217 child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool);
218 fs_path = svn_client__pathrev_fspath(child_pathrev, subpool);
219
220 lock = svn_hash_gets(locks, fs_path);
221
222 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
223 subpool));
224
225 if (depth >= svn_depth_immediates
226 || (depth == svn_depth_files && the_ent->kind == svn_node_file))
227 {
228 SVN_ERR(receiver(receiver_baton, path, info, subpool));
229 }
230
231 if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
232 {
233 SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
234 receiver, receiver_baton,
235 depth, ctx, locks, subpool));
236 }
237 }
238
239 svn_pool_destroy(subpool);
240
241 return SVN_NO_ERROR;
242 }
243
244
245 /* Set *SAME_P to TRUE if URL exists in the head of the repository and
246 refers to the same resource as it does in REV, using POOL for
247 temporary allocations. RA_SESSION is an open RA session for URL. */
248 static svn_error_t *
same_resource_in_head(svn_boolean_t * same_p,const char * url,svn_revnum_t rev,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * pool)249 same_resource_in_head(svn_boolean_t *same_p,
250 const char *url,
251 svn_revnum_t rev,
252 svn_ra_session_t *ra_session,
253 svn_client_ctx_t *ctx,
254 apr_pool_t *pool)
255 {
256 svn_error_t *err;
257 svn_opt_revision_t operative_rev, peg_rev;
258 const char *head_url;
259
260 peg_rev.kind = svn_opt_revision_head;
261 operative_rev.kind = svn_opt_revision_number;
262 operative_rev.value.number = rev;
263
264 err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
265 ra_session,
266 url, &peg_rev,
267 &operative_rev, NULL,
268 ctx, pool);
269 if (err &&
270 ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
271 (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY) ||
272 (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
273 {
274 svn_error_clear(err);
275 *same_p = FALSE;
276 return SVN_NO_ERROR;
277 }
278 else
279 SVN_ERR(err);
280
281 /* ### Currently, the URLs should always be equal, since we can't
282 ### walk forwards in history. */
283 *same_p = (strcmp(url, head_url) == 0);
284
285 return SVN_NO_ERROR;
286 }
287
288 /* A baton for wc_info_receiver(), containing the wrapped receiver. */
289 typedef struct wc_info_receiver_baton_t
290 {
291 svn_client_info_receiver2_t client_receiver_func;
292 void *client_receiver_baton;
293 } wc_info_receiver_baton_t;
294
295 /* A receiver for WC info, implementing svn_client_info_receiver2_t.
296 * Convert the WC info to client info and pass it to the client info
297 * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */
298 static svn_error_t *
wc_info_receiver(void * baton,const char * abspath_or_url,const svn_wc__info2_t * wc_info,apr_pool_t * scratch_pool)299 wc_info_receiver(void *baton,
300 const char *abspath_or_url,
301 const svn_wc__info2_t *wc_info,
302 apr_pool_t *scratch_pool)
303 {
304 wc_info_receiver_baton_t *b = baton;
305 svn_client_info2_t client_info;
306
307 /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */
308 client_info.repos_root_URL = wc_info->repos_root_URL;
309 client_info.repos_UUID = wc_info->repos_UUID;
310 client_info.rev = wc_info->rev;
311 client_info.URL = wc_info->URL;
312
313 client_info.kind = wc_info->kind;
314 client_info.size = wc_info->size;
315 client_info.last_changed_rev = wc_info->last_changed_rev;
316 client_info.last_changed_date = wc_info->last_changed_date;
317 client_info.last_changed_author = wc_info->last_changed_author;
318
319 client_info.lock = wc_info->lock;
320
321 client_info.wc_info = wc_info->wc_info;
322
323 return b->client_receiver_func(b->client_receiver_baton,
324 abspath_or_url, &client_info, scratch_pool);
325 }
326
327 svn_error_t *
svn_client_info4(const char * abspath_or_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t fetch_excluded,svn_boolean_t fetch_actual_only,svn_boolean_t include_externals,const apr_array_header_t * changelists,svn_client_info_receiver2_t receiver,void * receiver_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)328 svn_client_info4(const char *abspath_or_url,
329 const svn_opt_revision_t *peg_revision,
330 const svn_opt_revision_t *revision,
331 svn_depth_t depth,
332 svn_boolean_t fetch_excluded,
333 svn_boolean_t fetch_actual_only,
334 svn_boolean_t include_externals,
335 const apr_array_header_t *changelists,
336 svn_client_info_receiver2_t receiver,
337 void *receiver_baton,
338 svn_client_ctx_t *ctx,
339 apr_pool_t *pool)
340 {
341 svn_ra_session_t *ra_session;
342 svn_client__pathrev_t *pathrev;
343 svn_lock_t *lock;
344 svn_boolean_t related;
345 const char *base_name;
346 svn_dirent_t *the_ent;
347 svn_client_info2_t *info;
348 svn_error_t *err;
349
350 if (depth == svn_depth_unknown)
351 depth = svn_depth_empty;
352
353 if ((revision == NULL
354 || revision->kind == svn_opt_revision_unspecified)
355 && (peg_revision == NULL
356 || peg_revision->kind == svn_opt_revision_unspecified))
357 {
358 /* Do all digging in the working copy. */
359 wc_info_receiver_baton_t b;
360
361 b.client_receiver_func = receiver;
362 b.client_receiver_baton = receiver_baton;
363 SVN_ERR(svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth,
364 fetch_excluded, fetch_actual_only, changelists,
365 wc_info_receiver, &b,
366 ctx->cancel_func, ctx->cancel_baton, pool));
367
368 if (include_externals && SVN_DEPTH_IS_RECURSIVE(depth))
369 {
370 apr_hash_t *external_map;
371
372 SVN_ERR(svn_wc__externals_defined_below(&external_map,
373 ctx->wc_ctx, abspath_or_url,
374 pool, pool));
375
376 SVN_ERR(do_external_info(external_map,
377 depth, fetch_excluded, fetch_actual_only,
378 changelists,
379 receiver, receiver_baton, ctx, pool));
380 }
381
382 return SVN_NO_ERROR;
383 }
384
385 /* Go repository digging instead. */
386
387 /* Trace rename history (starting at path_or_url@peg_revision) and
388 return RA session to the possibly-renamed URL as it exists in REVISION.
389 The ra_session returned will be anchored on this "final" URL. */
390 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
391 abspath_or_url, NULL, peg_revision,
392 revision, ctx, pool));
393 base_name = svn_uri_basename(pathrev->url, pool);
394
395 /* Get the dirent for the URL itself. */
396 SVN_ERR(svn_ra_stat(ra_session, "", pathrev->rev, &the_ent, pool));
397
398 if (! the_ent)
399 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
400 _("URL '%s' non-existent in revision %ld"),
401 pathrev->url, pathrev->rev);
402
403 /* Check if the URL exists in HEAD and refers to the same resource.
404 In this case, we check the repository for a lock on this URL.
405
406 ### There is a possible race here, since HEAD might have changed since
407 ### we checked it. A solution to this problem could be to do the below
408 ### check in a loop which only terminates if the HEAD revision is the same
409 ### before and after this check. That could, however, lead to a
410 ### starvation situation instead. */
411 SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev,
412 ra_session, ctx, pool));
413 if (related)
414 {
415 err = svn_ra_get_lock(ra_session, &lock, "", pool);
416
417 /* An old mod_dav_svn will always work; there's nothing wrong with
418 doing a PROPFIND for a property named "DAV:supportedlock". But
419 an old svnserve will error. */
420 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
421 {
422 svn_error_clear(err);
423 lock = NULL;
424 }
425 else if (err)
426 return svn_error_trace(err);
427 }
428 else
429 lock = NULL;
430
431 /* Push the URL's dirent (and lock) at the callback.*/
432 SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool));
433 SVN_ERR(receiver(receiver_baton, base_name, info, pool));
434
435 /* Possibly recurse, using the original RA session. */
436 if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
437 {
438 apr_hash_t *locks;
439
440 if (peg_revision->kind == svn_opt_revision_head)
441 {
442 err = svn_ra_get_locks2(ra_session, &locks, "", depth,
443 pool);
444
445 /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
446 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
447 {
448 svn_error_clear(err);
449 locks = apr_hash_make(pool); /* use an empty hash */
450 }
451 else if (err)
452 return svn_error_trace(err);
453 }
454 else
455 locks = apr_hash_make(pool); /* use an empty hash */
456
457 SVN_ERR(push_dir_info(ra_session, pathrev, "",
458 receiver, receiver_baton,
459 depth, ctx, locks, pool));
460 }
461
462 return SVN_NO_ERROR;
463 }
464
465
466 svn_error_t *
svn_client_get_wc_root(const char ** wcroot_abspath,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)467 svn_client_get_wc_root(const char **wcroot_abspath,
468 const char *local_abspath,
469 svn_client_ctx_t *ctx,
470 apr_pool_t *result_pool,
471 apr_pool_t *scratch_pool)
472 {
473 return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
474 result_pool, scratch_pool);
475 }
476
477
478 /* NOTE: This function was requested by the TortoiseSVN project. See
479 issue #3927. */
480 svn_error_t *
svn_client_min_max_revisions(svn_revnum_t * min_revision,svn_revnum_t * max_revision,const char * local_abspath,svn_boolean_t committed,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)481 svn_client_min_max_revisions(svn_revnum_t *min_revision,
482 svn_revnum_t *max_revision,
483 const char *local_abspath,
484 svn_boolean_t committed,
485 svn_client_ctx_t *ctx,
486 apr_pool_t *scratch_pool)
487 {
488 return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
489 local_abspath, committed, scratch_pool);
490 }
491