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