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
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*/
183static svn_error_t *
184push_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.  */
247static svn_error_t *
248same_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. */
287typedef 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). */
296static svn_error_t *
297wc_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
325svn_error_t *
326svn_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
464svn_error_t *
465svn_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. */
478svn_error_t *
479svn_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}
489