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
43svn_client_info2_t *
44svn_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
66static svn_error_t *
67do_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. */
139static svn_error_t *
140build_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*/
184static svn_error_t *
185push_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.  */
248static svn_error_t *
249same_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. */
289typedef 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). */
298static svn_error_t *
299wc_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
327svn_error_t *
328svn_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
466svn_error_t *
467svn_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. */
480svn_error_t *
481svn_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