]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/info.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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_pools.h"
31 #include "svn_dirent_uri.h"
32 #include "svn_path.h"
33 #include "svn_hash.h"
34 #include "svn_wc.h"
35
36 #include "svn_private_config.h"
37 #include "private/svn_fspath.h"
38 #include "private/svn_wc_private.h"
39
40
41 svn_client_info2_t *
42 svn_client_info2_dup(const svn_client_info2_t *info,
43                      apr_pool_t *pool)
44 {
45   svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
46
47   if (new_info->URL)
48     new_info->URL = apr_pstrdup(pool, info->URL);
49   if (new_info->repos_root_URL)
50     new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
51   if (new_info->repos_UUID)
52     new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
53   if (info->last_changed_author)
54     new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author);
55   if (new_info->lock)
56     new_info->lock = svn_lock_dup(info->lock, pool);
57   if (new_info->wc_info)
58     new_info->wc_info = svn_wc_info_dup(info->wc_info, pool);
59   return new_info;
60 }
61
62 /* Set *INFO to a new info struct built from DIRENT
63    and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
64    Pointer fields are copied by reference, not dup'd. */
65 static svn_error_t *
66 build_info_from_dirent(svn_client_info2_t **info,
67                        const svn_dirent_t *dirent,
68                        svn_lock_t *lock,
69                        const svn_client__pathrev_t *pathrev,
70                        apr_pool_t *pool)
71 {
72   svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
73
74   tmpinfo->URL                  = pathrev->url;
75   tmpinfo->rev                  = pathrev->rev;
76   tmpinfo->kind                 = dirent->kind;
77   tmpinfo->repos_UUID           = pathrev->repos_uuid;
78   tmpinfo->repos_root_URL       = pathrev->repos_root_url;
79   tmpinfo->last_changed_rev     = dirent->created_rev;
80   tmpinfo->last_changed_date    = dirent->time;
81   tmpinfo->last_changed_author  = dirent->last_author;
82   tmpinfo->lock                 = lock;
83   tmpinfo->size                 = dirent->size;
84
85   tmpinfo->wc_info              = NULL;
86
87   *info = tmpinfo;
88   return SVN_NO_ERROR;
89 }
90
91
92 /* The dirent fields we care about for our calls to svn_ra_get_dir2. */
93 #define DIRENT_FIELDS (SVN_DIRENT_KIND        | \
94                        SVN_DIRENT_CREATED_REV | \
95                        SVN_DIRENT_TIME        | \
96                        SVN_DIRENT_LAST_AUTHOR)
97
98
99 /* Helper func for recursively fetching svn_dirent_t's from a remote
100    directory and pushing them at an info-receiver callback.
101
102    DEPTH is the depth starting at DIR, even though RECEIVER is never
103    invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
104    RECEIVER on all children of DIR, but none of their children; if
105    svn_depth_files, then invoke RECEIVER on file children of DIR but
106    not on subdirectories; if svn_depth_infinity, recurse fully.
107    DIR is a relpath, relative to the root of RA_SESSION.
108 */
109 static svn_error_t *
110 push_dir_info(svn_ra_session_t *ra_session,
111               const svn_client__pathrev_t *pathrev,
112               const char *dir,
113               svn_client_info_receiver2_t receiver,
114               void *receiver_baton,
115               svn_depth_t depth,
116               svn_client_ctx_t *ctx,
117               apr_hash_t *locks,
118               apr_pool_t *pool)
119 {
120   apr_hash_t *tmpdirents;
121   apr_hash_index_t *hi;
122   apr_pool_t *subpool = svn_pool_create(pool);
123
124   SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
125                           dir, pathrev->rev, DIRENT_FIELDS, pool));
126
127   for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
128     {
129       const char *path, *fs_path;
130       svn_lock_t *lock;
131       svn_client_info2_t *info;
132       const char *name = svn__apr_hash_index_key(hi);
133       svn_dirent_t *the_ent = svn__apr_hash_index_val(hi);
134       svn_client__pathrev_t *child_pathrev;
135
136       svn_pool_clear(subpool);
137
138       if (ctx->cancel_func)
139         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
140
141       path = svn_relpath_join(dir, name, subpool);
142       child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool);
143       fs_path = svn_client__pathrev_fspath(child_pathrev, subpool);
144
145       lock = svn_hash_gets(locks, fs_path);
146
147       SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
148                                      subpool));
149
150       if (depth >= svn_depth_immediates
151           || (depth == svn_depth_files && the_ent->kind == svn_node_file))
152         {
153           SVN_ERR(receiver(receiver_baton, path, info, subpool));
154         }
155
156       if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
157         {
158           SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
159                                 receiver, receiver_baton,
160                                 depth, ctx, locks, subpool));
161         }
162     }
163
164   svn_pool_destroy(subpool);
165
166   return SVN_NO_ERROR;
167 }
168
169
170 /* Set *SAME_P to TRUE if URL exists in the head of the repository and
171    refers to the same resource as it does in REV, using POOL for
172    temporary allocations.  RA_SESSION is an open RA session for URL.  */
173 static svn_error_t *
174 same_resource_in_head(svn_boolean_t *same_p,
175                       const char *url,
176                       svn_revnum_t rev,
177                       svn_ra_session_t *ra_session,
178                       svn_client_ctx_t *ctx,
179                       apr_pool_t *pool)
180 {
181   svn_error_t *err;
182   svn_opt_revision_t start_rev, peg_rev;
183   const char *head_url;
184
185   start_rev.kind = svn_opt_revision_head;
186   peg_rev.kind = svn_opt_revision_number;
187   peg_rev.value.number = rev;
188
189   err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
190                                     ra_session,
191                                     url, &peg_rev,
192                                     &start_rev, NULL,
193                                     ctx, pool);
194   if (err &&
195       ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
196        (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
197     {
198       svn_error_clear(err);
199       *same_p = FALSE;
200       return SVN_NO_ERROR;
201     }
202   else
203     SVN_ERR(err);
204
205   /* ### Currently, the URLs should always be equal, since we can't
206      ### walk forwards in history. */
207   *same_p = (strcmp(url, head_url) == 0);
208
209   return SVN_NO_ERROR;
210 }
211
212 /* A baton for wc_info_receiver(), containing the wrapped receiver. */
213 typedef struct wc_info_receiver_baton_t
214 {
215   svn_client_info_receiver2_t client_receiver_func;
216   void *client_receiver_baton;
217 } wc_info_receiver_baton_t;
218
219 /* A receiver for WC info, implementing svn_client_info_receiver2_t.
220  * Convert the WC info to client info and pass it to the client info
221  * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */
222 static svn_error_t *
223 wc_info_receiver(void *baton,
224                  const char *abspath_or_url,
225                  const svn_wc__info2_t *wc_info,
226                  apr_pool_t *scratch_pool)
227 {
228   wc_info_receiver_baton_t *b = baton;
229   svn_client_info2_t client_info;
230
231   /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */
232   client_info.repos_root_URL = wc_info->repos_root_URL;
233   client_info.repos_UUID = wc_info->repos_UUID;
234   client_info.rev = wc_info->rev;
235   client_info.URL = wc_info->URL;
236
237   client_info.kind = wc_info->kind;
238   client_info.size = wc_info->size;
239   client_info.last_changed_rev = wc_info->last_changed_rev;
240   client_info.last_changed_date = wc_info->last_changed_date;
241   client_info.last_changed_author = wc_info->last_changed_author;
242
243   client_info.lock = wc_info->lock;
244
245   client_info.wc_info = wc_info->wc_info;
246
247   return b->client_receiver_func(b->client_receiver_baton,
248                                  abspath_or_url, &client_info, scratch_pool);
249 }
250
251 svn_error_t *
252 svn_client_info3(const char *abspath_or_url,
253                  const svn_opt_revision_t *peg_revision,
254                  const svn_opt_revision_t *revision,
255                  svn_depth_t depth,
256                  svn_boolean_t fetch_excluded,
257                  svn_boolean_t fetch_actual_only,
258                  const apr_array_header_t *changelists,
259                  svn_client_info_receiver2_t receiver,
260                  void *receiver_baton,
261                  svn_client_ctx_t *ctx,
262                  apr_pool_t *pool)
263 {
264   svn_ra_session_t *ra_session;
265   svn_client__pathrev_t *pathrev;
266   svn_lock_t *lock;
267   svn_boolean_t related;
268   const char *base_name;
269   svn_dirent_t *the_ent;
270   svn_client_info2_t *info;
271   svn_error_t *err;
272
273   if (depth == svn_depth_unknown)
274     depth = svn_depth_empty;
275
276   if ((revision == NULL
277        || revision->kind == svn_opt_revision_unspecified)
278       && (peg_revision == NULL
279           || peg_revision->kind == svn_opt_revision_unspecified))
280     {
281       /* Do all digging in the working copy. */
282       wc_info_receiver_baton_t b;
283
284       b.client_receiver_func = receiver;
285       b.client_receiver_baton = receiver_baton;
286       return svn_error_trace(
287         svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth,
288                         fetch_excluded, fetch_actual_only, changelists,
289                          wc_info_receiver, &b,
290                          ctx->cancel_func, ctx->cancel_baton, pool));
291     }
292
293   /* Go repository digging instead. */
294
295   /* Trace rename history (starting at path_or_url@peg_revision) and
296      return RA session to the possibly-renamed URL as it exists in REVISION.
297      The ra_session returned will be anchored on this "final" URL. */
298   SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
299                                             abspath_or_url, NULL, peg_revision,
300                                             revision, ctx, pool));
301
302   svn_uri_split(NULL, &base_name, pathrev->url, pool);
303
304   /* Get the dirent for the URL itself. */
305   SVN_ERR(svn_client__ra_stat_compatible(ra_session, pathrev->rev, &the_ent,
306                                          DIRENT_FIELDS, ctx, pool));
307
308   if (! the_ent)
309     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
310                              _("URL '%s' non-existent in revision %ld"),
311                              pathrev->url, pathrev->rev);
312
313   /* Check if the URL exists in HEAD and refers to the same resource.
314      In this case, we check the repository for a lock on this URL.
315
316      ### There is a possible race here, since HEAD might have changed since
317      ### we checked it.  A solution to this problem could be to do the below
318      ### check in a loop which only terminates if the HEAD revision is the same
319      ### before and after this check.  That could, however, lead to a
320      ### starvation situation instead.  */
321   SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev,
322                                 ra_session, ctx, pool));
323   if (related)
324     {
325       err = svn_ra_get_lock(ra_session, &lock, "", pool);
326
327       /* An old mod_dav_svn will always work; there's nothing wrong with
328          doing a PROPFIND for a property named "DAV:supportedlock". But
329          an old svnserve will error. */
330       if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
331         {
332           svn_error_clear(err);
333           lock = NULL;
334         }
335       else if (err)
336         return svn_error_trace(err);
337     }
338   else
339     lock = NULL;
340
341   /* Push the URL's dirent (and lock) at the callback.*/
342   SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool));
343   SVN_ERR(receiver(receiver_baton, base_name, info, pool));
344
345   /* Possibly recurse, using the original RA session. */
346   if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
347     {
348       apr_hash_t *locks;
349
350       if (peg_revision->kind == svn_opt_revision_head)
351         {
352           err = svn_ra_get_locks2(ra_session, &locks, "", depth,
353                                   pool);
354
355           /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
356           if (err &&
357               (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED
358                || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
359             {
360               svn_error_clear(err);
361               locks = apr_hash_make(pool); /* use an empty hash */
362             }
363           else if (err)
364             return svn_error_trace(err);
365         }
366       else
367         locks = apr_hash_make(pool); /* use an empty hash */
368
369       SVN_ERR(push_dir_info(ra_session, pathrev, "",
370                             receiver, receiver_baton,
371                             depth, ctx, locks, pool));
372     }
373
374   return SVN_NO_ERROR;
375 }
376
377
378 svn_error_t *
379 svn_client_get_wc_root(const char **wcroot_abspath,
380                        const char *local_abspath,
381                        svn_client_ctx_t *ctx,
382                        apr_pool_t *result_pool,
383                        apr_pool_t *scratch_pool)
384 {
385   return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
386                             result_pool, scratch_pool);
387 }
388
389
390 /* NOTE: This function was requested by the TortoiseSVN project.  See
391    issue #3927. */
392 svn_error_t *
393 svn_client_min_max_revisions(svn_revnum_t *min_revision,
394                              svn_revnum_t *max_revision,
395                              const char *local_abspath,
396                              svn_boolean_t committed,
397                              svn_client_ctx_t *ctx,
398                              apr_pool_t *scratch_pool)
399 {
400   return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
401                                    local_abspath, committed, scratch_pool);
402 }