]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/info.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_client / info.c
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 *
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 *
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 *
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
172
173 /* Helper func for recursively fetching svn_dirent_t's from a remote
174    directory and pushing them at an info-receiver callback.
175
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.
182 */
183 static svn_error_t *
184 push_dir_info(svn_ra_session_t *ra_session,
185               const svn_client__pathrev_t *pathrev,
186               const char *dir,
187               svn_client_info_receiver2_t receiver,
188               void *receiver_baton,
189               svn_depth_t depth,
190               svn_client_ctx_t *ctx,
191               apr_hash_t *locks,
192               apr_pool_t *pool)
193 {
194   apr_hash_t *tmpdirents;
195   apr_hash_index_t *hi;
196   apr_pool_t *subpool = svn_pool_create(pool);
197
198   SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
199                           dir, pathrev->rev, DIRENT_FIELDS, pool));
200
201   for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
202     {
203       const char *path, *fs_path;
204       svn_lock_t *lock;
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;
209
210       svn_pool_clear(subpool);
211
212       if (ctx->cancel_func)
213         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
214
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);
218
219       lock = svn_hash_gets(locks, fs_path);
220
221       SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
222                                      subpool));
223
224       if (depth >= svn_depth_immediates
225           || (depth == svn_depth_files && the_ent->kind == svn_node_file))
226         {
227           SVN_ERR(receiver(receiver_baton, path, info, subpool));
228         }
229
230       if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
231         {
232           SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
233                                 receiver, receiver_baton,
234                                 depth, ctx, locks, subpool));
235         }
236     }
237
238   svn_pool_destroy(subpool);
239
240   return SVN_NO_ERROR;
241 }
242
243
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.  */
247 static svn_error_t *
248 same_resource_in_head(svn_boolean_t *same_p,
249                       const char *url,
250                       svn_revnum_t rev,
251                       svn_ra_session_t *ra_session,
252                       svn_client_ctx_t *ctx,
253                       apr_pool_t *pool)
254 {
255   svn_error_t *err;
256   svn_opt_revision_t start_rev, peg_rev;
257   const char *head_url;
258
259   start_rev.kind = svn_opt_revision_head;
260   peg_rev.kind = svn_opt_revision_number;
261   peg_rev.value.number = rev;
262
263   err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
264                                     ra_session,
265                                     url, &peg_rev,
266                                     &start_rev, NULL,
267                                     ctx, pool);
268   if (err &&
269       ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
270        (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
271     {
272       svn_error_clear(err);
273       *same_p = FALSE;
274       return SVN_NO_ERROR;
275     }
276   else
277     SVN_ERR(err);
278
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);
282
283   return SVN_NO_ERROR;
284 }
285
286 /* A baton for wc_info_receiver(), containing the wrapped receiver. */
287 typedef struct wc_info_receiver_baton_t
288 {
289   svn_client_info_receiver2_t client_receiver_func;
290   void *client_receiver_baton;
291 } wc_info_receiver_baton_t;
292
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). */
296 static svn_error_t *
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)
301 {
302   wc_info_receiver_baton_t *b = baton;
303   svn_client_info2_t client_info;
304
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;
310
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;
316
317   client_info.lock = wc_info->lock;
318
319   client_info.wc_info = wc_info->wc_info;
320
321   return b->client_receiver_func(b->client_receiver_baton,
322                                  abspath_or_url, &client_info, scratch_pool);
323 }
324
325 svn_error_t *
326 svn_client_info4(const char *abspath_or_url,
327                  const svn_opt_revision_t *peg_revision,
328                  const svn_opt_revision_t *revision,
329                  svn_depth_t depth,
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,
337                  apr_pool_t *pool)
338 {
339   svn_ra_session_t *ra_session;
340   svn_client__pathrev_t *pathrev;
341   svn_lock_t *lock;
342   svn_boolean_t related;
343   const char *base_name;
344   svn_dirent_t *the_ent;
345   svn_client_info2_t *info;
346   svn_error_t *err;
347
348   if (depth == svn_depth_unknown)
349     depth = svn_depth_empty;
350
351   if ((revision == NULL
352        || revision->kind == svn_opt_revision_unspecified)
353       && (peg_revision == NULL
354           || peg_revision->kind == svn_opt_revision_unspecified))
355     {
356       /* Do all digging in the working copy. */
357       wc_info_receiver_baton_t b;
358
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));
365
366       if (include_externals && SVN_DEPTH_IS_RECURSIVE(depth))
367         {
368           apr_hash_t *external_map;
369
370           SVN_ERR(svn_wc__externals_defined_below(&external_map,
371                                                   ctx->wc_ctx, abspath_or_url,
372                                                   pool, pool));
373
374           SVN_ERR(do_external_info(external_map,
375                                    depth, fetch_excluded, fetch_actual_only,
376                                    changelists,
377                                    receiver, receiver_baton, ctx, pool));
378         }
379
380       return SVN_NO_ERROR;
381     }
382
383   /* Go repository digging instead. */
384
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);
392
393   /* Get the dirent for the URL itself. */
394   SVN_ERR(svn_ra_stat(ra_session, "", pathrev->rev, &the_ent, pool));
395
396   if (! the_ent)
397     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
398                              _("URL '%s' non-existent in revision %ld"),
399                              pathrev->url, pathrev->rev);
400
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.
403
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));
411   if (related)
412     {
413       err = svn_ra_get_lock(ra_session, &lock, "", pool);
414
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)
419         {
420           svn_error_clear(err);
421           lock = NULL;
422         }
423       else if (err)
424         return svn_error_trace(err);
425     }
426   else
427     lock = NULL;
428
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));
432
433   /* Possibly recurse, using the original RA session. */
434   if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
435     {
436       apr_hash_t *locks;
437
438       if (peg_revision->kind == svn_opt_revision_head)
439         {
440           err = svn_ra_get_locks2(ra_session, &locks, "", depth,
441                                   pool);
442
443           /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
444           if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
445             {
446               svn_error_clear(err);
447               locks = apr_hash_make(pool); /* use an empty hash */
448             }
449           else if (err)
450             return svn_error_trace(err);
451         }
452       else
453         locks = apr_hash_make(pool); /* use an empty hash */
454
455       SVN_ERR(push_dir_info(ra_session, pathrev, "",
456                             receiver, receiver_baton,
457                             depth, ctx, locks, pool));
458     }
459
460   return SVN_NO_ERROR;
461 }
462
463
464 svn_error_t *
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)
470 {
471   return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
472                             result_pool, scratch_pool);
473 }
474
475
476 /* NOTE: This function was requested by the TortoiseSVN project.  See
477    issue #3927. */
478 svn_error_t *
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)
485 {
486   return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
487                                    local_abspath, committed, scratch_pool);
488 }