1251881Speter/*
2251881Speter * info.c:  return system-generated metadata about paths or URLs.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter#include "client.h"
29251881Speter#include "svn_client.h"
30251881Speter#include "svn_pools.h"
31251881Speter#include "svn_dirent_uri.h"
32251881Speter#include "svn_path.h"
33251881Speter#include "svn_hash.h"
34251881Speter#include "svn_wc.h"
35251881Speter
36251881Speter#include "svn_private_config.h"
37251881Speter#include "private/svn_fspath.h"
38251881Speter#include "private/svn_wc_private.h"
39251881Speter
40251881Speter
41251881Spetersvn_client_info2_t *
42251881Spetersvn_client_info2_dup(const svn_client_info2_t *info,
43251881Speter                     apr_pool_t *pool)
44251881Speter{
45251881Speter  svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
46251881Speter
47251881Speter  if (new_info->URL)
48251881Speter    new_info->URL = apr_pstrdup(pool, info->URL);
49251881Speter  if (new_info->repos_root_URL)
50251881Speter    new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
51251881Speter  if (new_info->repos_UUID)
52251881Speter    new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
53251881Speter  if (info->last_changed_author)
54251881Speter    new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author);
55251881Speter  if (new_info->lock)
56251881Speter    new_info->lock = svn_lock_dup(info->lock, pool);
57251881Speter  if (new_info->wc_info)
58251881Speter    new_info->wc_info = svn_wc_info_dup(info->wc_info, pool);
59251881Speter  return new_info;
60251881Speter}
61251881Speter
62251881Speter/* Set *INFO to a new info struct built from DIRENT
63251881Speter   and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
64251881Speter   Pointer fields are copied by reference, not dup'd. */
65251881Speterstatic svn_error_t *
66251881Speterbuild_info_from_dirent(svn_client_info2_t **info,
67251881Speter                       const svn_dirent_t *dirent,
68251881Speter                       svn_lock_t *lock,
69251881Speter                       const svn_client__pathrev_t *pathrev,
70251881Speter                       apr_pool_t *pool)
71251881Speter{
72251881Speter  svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
73251881Speter
74251881Speter  tmpinfo->URL                  = pathrev->url;
75251881Speter  tmpinfo->rev                  = pathrev->rev;
76251881Speter  tmpinfo->kind                 = dirent->kind;
77251881Speter  tmpinfo->repos_UUID           = pathrev->repos_uuid;
78251881Speter  tmpinfo->repos_root_URL       = pathrev->repos_root_url;
79251881Speter  tmpinfo->last_changed_rev     = dirent->created_rev;
80251881Speter  tmpinfo->last_changed_date    = dirent->time;
81251881Speter  tmpinfo->last_changed_author  = dirent->last_author;
82251881Speter  tmpinfo->lock                 = lock;
83251881Speter  tmpinfo->size                 = dirent->size;
84251881Speter
85251881Speter  tmpinfo->wc_info              = NULL;
86251881Speter
87251881Speter  *info = tmpinfo;
88251881Speter  return SVN_NO_ERROR;
89251881Speter}
90251881Speter
91251881Speter
92251881Speter/* The dirent fields we care about for our calls to svn_ra_get_dir2. */
93251881Speter#define DIRENT_FIELDS (SVN_DIRENT_KIND        | \
94251881Speter                       SVN_DIRENT_CREATED_REV | \
95251881Speter                       SVN_DIRENT_TIME        | \
96251881Speter                       SVN_DIRENT_LAST_AUTHOR)
97251881Speter
98251881Speter
99251881Speter/* Helper func for recursively fetching svn_dirent_t's from a remote
100251881Speter   directory and pushing them at an info-receiver callback.
101251881Speter
102251881Speter   DEPTH is the depth starting at DIR, even though RECEIVER is never
103251881Speter   invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
104251881Speter   RECEIVER on all children of DIR, but none of their children; if
105251881Speter   svn_depth_files, then invoke RECEIVER on file children of DIR but
106251881Speter   not on subdirectories; if svn_depth_infinity, recurse fully.
107251881Speter   DIR is a relpath, relative to the root of RA_SESSION.
108251881Speter*/
109251881Speterstatic svn_error_t *
110251881Speterpush_dir_info(svn_ra_session_t *ra_session,
111251881Speter              const svn_client__pathrev_t *pathrev,
112251881Speter              const char *dir,
113251881Speter              svn_client_info_receiver2_t receiver,
114251881Speter              void *receiver_baton,
115251881Speter              svn_depth_t depth,
116251881Speter              svn_client_ctx_t *ctx,
117251881Speter              apr_hash_t *locks,
118251881Speter              apr_pool_t *pool)
119251881Speter{
120251881Speter  apr_hash_t *tmpdirents;
121251881Speter  apr_hash_index_t *hi;
122251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
123251881Speter
124251881Speter  SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
125251881Speter                          dir, pathrev->rev, DIRENT_FIELDS, pool));
126251881Speter
127251881Speter  for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
128251881Speter    {
129251881Speter      const char *path, *fs_path;
130251881Speter      svn_lock_t *lock;
131251881Speter      svn_client_info2_t *info;
132251881Speter      const char *name = svn__apr_hash_index_key(hi);
133251881Speter      svn_dirent_t *the_ent = svn__apr_hash_index_val(hi);
134251881Speter      svn_client__pathrev_t *child_pathrev;
135251881Speter
136251881Speter      svn_pool_clear(subpool);
137251881Speter
138251881Speter      if (ctx->cancel_func)
139251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
140251881Speter
141251881Speter      path = svn_relpath_join(dir, name, subpool);
142251881Speter      child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool);
143251881Speter      fs_path = svn_client__pathrev_fspath(child_pathrev, subpool);
144251881Speter
145251881Speter      lock = svn_hash_gets(locks, fs_path);
146251881Speter
147251881Speter      SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
148251881Speter                                     subpool));
149251881Speter
150251881Speter      if (depth >= svn_depth_immediates
151251881Speter          || (depth == svn_depth_files && the_ent->kind == svn_node_file))
152251881Speter        {
153251881Speter          SVN_ERR(receiver(receiver_baton, path, info, subpool));
154251881Speter        }
155251881Speter
156251881Speter      if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
157251881Speter        {
158251881Speter          SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
159251881Speter                                receiver, receiver_baton,
160251881Speter                                depth, ctx, locks, subpool));
161251881Speter        }
162251881Speter    }
163251881Speter
164251881Speter  svn_pool_destroy(subpool);
165251881Speter
166251881Speter  return SVN_NO_ERROR;
167251881Speter}
168251881Speter
169251881Speter
170251881Speter/* Set *SAME_P to TRUE if URL exists in the head of the repository and
171251881Speter   refers to the same resource as it does in REV, using POOL for
172251881Speter   temporary allocations.  RA_SESSION is an open RA session for URL.  */
173251881Speterstatic svn_error_t *
174251881Spetersame_resource_in_head(svn_boolean_t *same_p,
175251881Speter                      const char *url,
176251881Speter                      svn_revnum_t rev,
177251881Speter                      svn_ra_session_t *ra_session,
178251881Speter                      svn_client_ctx_t *ctx,
179251881Speter                      apr_pool_t *pool)
180251881Speter{
181251881Speter  svn_error_t *err;
182251881Speter  svn_opt_revision_t start_rev, peg_rev;
183251881Speter  const char *head_url;
184251881Speter
185251881Speter  start_rev.kind = svn_opt_revision_head;
186251881Speter  peg_rev.kind = svn_opt_revision_number;
187251881Speter  peg_rev.value.number = rev;
188251881Speter
189251881Speter  err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
190251881Speter                                    ra_session,
191251881Speter                                    url, &peg_rev,
192251881Speter                                    &start_rev, NULL,
193251881Speter                                    ctx, pool);
194251881Speter  if (err &&
195251881Speter      ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
196251881Speter       (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
197251881Speter    {
198251881Speter      svn_error_clear(err);
199251881Speter      *same_p = FALSE;
200251881Speter      return SVN_NO_ERROR;
201251881Speter    }
202251881Speter  else
203251881Speter    SVN_ERR(err);
204251881Speter
205251881Speter  /* ### Currently, the URLs should always be equal, since we can't
206251881Speter     ### walk forwards in history. */
207251881Speter  *same_p = (strcmp(url, head_url) == 0);
208251881Speter
209251881Speter  return SVN_NO_ERROR;
210251881Speter}
211251881Speter
212251881Speter/* A baton for wc_info_receiver(), containing the wrapped receiver. */
213251881Spetertypedef struct wc_info_receiver_baton_t
214251881Speter{
215251881Speter  svn_client_info_receiver2_t client_receiver_func;
216251881Speter  void *client_receiver_baton;
217251881Speter} wc_info_receiver_baton_t;
218251881Speter
219251881Speter/* A receiver for WC info, implementing svn_client_info_receiver2_t.
220251881Speter * Convert the WC info to client info and pass it to the client info
221251881Speter * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */
222251881Speterstatic svn_error_t *
223251881Speterwc_info_receiver(void *baton,
224251881Speter                 const char *abspath_or_url,
225251881Speter                 const svn_wc__info2_t *wc_info,
226251881Speter                 apr_pool_t *scratch_pool)
227251881Speter{
228251881Speter  wc_info_receiver_baton_t *b = baton;
229251881Speter  svn_client_info2_t client_info;
230251881Speter
231251881Speter  /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */
232251881Speter  client_info.repos_root_URL = wc_info->repos_root_URL;
233251881Speter  client_info.repos_UUID = wc_info->repos_UUID;
234251881Speter  client_info.rev = wc_info->rev;
235251881Speter  client_info.URL = wc_info->URL;
236251881Speter
237251881Speter  client_info.kind = wc_info->kind;
238251881Speter  client_info.size = wc_info->size;
239251881Speter  client_info.last_changed_rev = wc_info->last_changed_rev;
240251881Speter  client_info.last_changed_date = wc_info->last_changed_date;
241251881Speter  client_info.last_changed_author = wc_info->last_changed_author;
242251881Speter
243251881Speter  client_info.lock = wc_info->lock;
244251881Speter
245251881Speter  client_info.wc_info = wc_info->wc_info;
246251881Speter
247251881Speter  return b->client_receiver_func(b->client_receiver_baton,
248251881Speter                                 abspath_or_url, &client_info, scratch_pool);
249251881Speter}
250251881Speter
251251881Spetersvn_error_t *
252251881Spetersvn_client_info3(const char *abspath_or_url,
253251881Speter                 const svn_opt_revision_t *peg_revision,
254251881Speter                 const svn_opt_revision_t *revision,
255251881Speter                 svn_depth_t depth,
256251881Speter                 svn_boolean_t fetch_excluded,
257251881Speter                 svn_boolean_t fetch_actual_only,
258251881Speter                 const apr_array_header_t *changelists,
259251881Speter                 svn_client_info_receiver2_t receiver,
260251881Speter                 void *receiver_baton,
261251881Speter                 svn_client_ctx_t *ctx,
262251881Speter                 apr_pool_t *pool)
263251881Speter{
264251881Speter  svn_ra_session_t *ra_session;
265251881Speter  svn_client__pathrev_t *pathrev;
266251881Speter  svn_lock_t *lock;
267251881Speter  svn_boolean_t related;
268251881Speter  const char *base_name;
269251881Speter  svn_dirent_t *the_ent;
270251881Speter  svn_client_info2_t *info;
271251881Speter  svn_error_t *err;
272251881Speter
273251881Speter  if (depth == svn_depth_unknown)
274251881Speter    depth = svn_depth_empty;
275251881Speter
276251881Speter  if ((revision == NULL
277251881Speter       || revision->kind == svn_opt_revision_unspecified)
278251881Speter      && (peg_revision == NULL
279251881Speter          || peg_revision->kind == svn_opt_revision_unspecified))
280251881Speter    {
281251881Speter      /* Do all digging in the working copy. */
282251881Speter      wc_info_receiver_baton_t b;
283251881Speter
284251881Speter      b.client_receiver_func = receiver;
285251881Speter      b.client_receiver_baton = receiver_baton;
286251881Speter      return svn_error_trace(
287251881Speter        svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth,
288251881Speter                        fetch_excluded, fetch_actual_only, changelists,
289251881Speter                         wc_info_receiver, &b,
290251881Speter                         ctx->cancel_func, ctx->cancel_baton, pool));
291251881Speter    }
292251881Speter
293251881Speter  /* Go repository digging instead. */
294251881Speter
295251881Speter  /* Trace rename history (starting at path_or_url@peg_revision) and
296251881Speter     return RA session to the possibly-renamed URL as it exists in REVISION.
297251881Speter     The ra_session returned will be anchored on this "final" URL. */
298251881Speter  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
299251881Speter                                            abspath_or_url, NULL, peg_revision,
300251881Speter                                            revision, ctx, pool));
301251881Speter
302251881Speter  svn_uri_split(NULL, &base_name, pathrev->url, pool);
303251881Speter
304251881Speter  /* Get the dirent for the URL itself. */
305251881Speter  SVN_ERR(svn_client__ra_stat_compatible(ra_session, pathrev->rev, &the_ent,
306251881Speter                                         DIRENT_FIELDS, ctx, pool));
307251881Speter
308251881Speter  if (! the_ent)
309251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
310251881Speter                             _("URL '%s' non-existent in revision %ld"),
311251881Speter                             pathrev->url, pathrev->rev);
312251881Speter
313251881Speter  /* Check if the URL exists in HEAD and refers to the same resource.
314251881Speter     In this case, we check the repository for a lock on this URL.
315251881Speter
316251881Speter     ### There is a possible race here, since HEAD might have changed since
317251881Speter     ### we checked it.  A solution to this problem could be to do the below
318251881Speter     ### check in a loop which only terminates if the HEAD revision is the same
319251881Speter     ### before and after this check.  That could, however, lead to a
320251881Speter     ### starvation situation instead.  */
321251881Speter  SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev,
322251881Speter                                ra_session, ctx, pool));
323251881Speter  if (related)
324251881Speter    {
325251881Speter      err = svn_ra_get_lock(ra_session, &lock, "", pool);
326251881Speter
327251881Speter      /* An old mod_dav_svn will always work; there's nothing wrong with
328251881Speter         doing a PROPFIND for a property named "DAV:supportedlock". But
329251881Speter         an old svnserve will error. */
330251881Speter      if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
331251881Speter        {
332251881Speter          svn_error_clear(err);
333251881Speter          lock = NULL;
334251881Speter        }
335251881Speter      else if (err)
336251881Speter        return svn_error_trace(err);
337251881Speter    }
338251881Speter  else
339251881Speter    lock = NULL;
340251881Speter
341251881Speter  /* Push the URL's dirent (and lock) at the callback.*/
342251881Speter  SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool));
343251881Speter  SVN_ERR(receiver(receiver_baton, base_name, info, pool));
344251881Speter
345251881Speter  /* Possibly recurse, using the original RA session. */
346251881Speter  if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
347251881Speter    {
348251881Speter      apr_hash_t *locks;
349251881Speter
350251881Speter      if (peg_revision->kind == svn_opt_revision_head)
351251881Speter        {
352251881Speter          err = svn_ra_get_locks2(ra_session, &locks, "", depth,
353251881Speter                                  pool);
354251881Speter
355251881Speter          /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
356251881Speter          if (err &&
357251881Speter              (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED
358251881Speter               || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
359251881Speter            {
360251881Speter              svn_error_clear(err);
361251881Speter              locks = apr_hash_make(pool); /* use an empty hash */
362251881Speter            }
363251881Speter          else if (err)
364251881Speter            return svn_error_trace(err);
365251881Speter        }
366251881Speter      else
367251881Speter        locks = apr_hash_make(pool); /* use an empty hash */
368251881Speter
369251881Speter      SVN_ERR(push_dir_info(ra_session, pathrev, "",
370251881Speter                            receiver, receiver_baton,
371251881Speter                            depth, ctx, locks, pool));
372251881Speter    }
373251881Speter
374251881Speter  return SVN_NO_ERROR;
375251881Speter}
376251881Speter
377251881Speter
378251881Spetersvn_error_t *
379251881Spetersvn_client_get_wc_root(const char **wcroot_abspath,
380251881Speter                       const char *local_abspath,
381251881Speter                       svn_client_ctx_t *ctx,
382251881Speter                       apr_pool_t *result_pool,
383251881Speter                       apr_pool_t *scratch_pool)
384251881Speter{
385251881Speter  return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
386251881Speter                            result_pool, scratch_pool);
387251881Speter}
388251881Speter
389251881Speter
390251881Speter/* NOTE: This function was requested by the TortoiseSVN project.  See
391251881Speter   issue #3927. */
392251881Spetersvn_error_t *
393251881Spetersvn_client_min_max_revisions(svn_revnum_t *min_revision,
394251881Speter                             svn_revnum_t *max_revision,
395251881Speter                             const char *local_abspath,
396251881Speter                             svn_boolean_t committed,
397251881Speter                             svn_client_ctx_t *ctx,
398251881Speter                             apr_pool_t *scratch_pool)
399251881Speter{
400251881Speter  return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
401251881Speter                                   local_abspath, committed, scratch_pool);
402251881Speter}
403