]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_wc/info.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_wc / info.c
1 /**
2  * @copyright
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  * @endcopyright
22  */
23
24 #include "svn_dirent_uri.h"
25 #include "svn_hash.h"
26 #include "svn_path.h"
27 #include "svn_pools.h"
28 #include "svn_wc.h"
29
30 #include "wc.h"
31
32 #include "svn_private_config.h"
33 #include "private/svn_wc_private.h"
34
35
36 \f
37 svn_wc_info_t *
38 svn_wc_info_dup(const svn_wc_info_t *info,
39                 apr_pool_t *pool)
40 {
41   svn_wc_info_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
42
43   if (info->changelist)
44     new_info->changelist = apr_pstrdup(pool, info->changelist);
45   new_info->checksum = svn_checksum_dup(info->checksum, pool);
46   if (info->conflicts)
47     {
48       int i;
49
50       apr_array_header_t *new_conflicts
51         = apr_array_make(pool, info->conflicts->nelts, info->conflicts->elt_size);
52       for (i = 0; i < info->conflicts->nelts; i++)
53         {
54           APR_ARRAY_PUSH(new_conflicts, svn_wc_conflict_description2_t *)
55             = svn_wc__conflict_description2_dup(
56                 APR_ARRAY_IDX(info->conflicts, i,
57                               const svn_wc_conflict_description2_t *),
58                 pool);
59         }
60       new_info->conflicts = new_conflicts;
61     }
62   if (info->copyfrom_url)
63     new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url);
64   if (info->wcroot_abspath)
65     new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath);
66   if (info->moved_from_abspath)
67     new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath);
68   if (info->moved_to_abspath)
69     new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath);
70
71   return new_info;
72 }
73
74
75 /* Set *INFO to a new struct, allocated in RESULT_POOL, built from the WC
76    metadata of LOCAL_ABSPATH.  Pointer fields are copied by reference, not
77    dup'd. */
78 static svn_error_t *
79 build_info_for_node(svn_wc__info2_t **info,
80                      svn_wc__db_t *db,
81                      const char *local_abspath,
82                      svn_node_kind_t kind,
83                      apr_pool_t *result_pool,
84                      apr_pool_t *scratch_pool)
85 {
86   svn_wc__info2_t *tmpinfo;
87   const char *repos_relpath;
88   svn_wc__db_status_t status;
89   svn_node_kind_t db_kind;
90   const char *original_repos_relpath;
91   const char *original_repos_root_url;
92   const char *original_uuid;
93   svn_revnum_t original_revision;
94   svn_wc__db_lock_t *lock;
95   svn_boolean_t conflicted;
96   svn_boolean_t op_root;
97   svn_boolean_t have_base;
98   svn_boolean_t have_more_work;
99   svn_wc_info_t *wc_info;
100
101   tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo));
102   tmpinfo->kind = kind;
103
104   wc_info = apr_pcalloc(result_pool, sizeof(*wc_info));
105   tmpinfo->wc_info = wc_info;
106
107   wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
108
109   SVN_ERR(svn_wc__db_read_info(&status, &db_kind, &tmpinfo->rev,
110                                &repos_relpath,
111                                &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID,
112                                &tmpinfo->last_changed_rev,
113                                &tmpinfo->last_changed_date,
114                                &tmpinfo->last_changed_author,
115                                &wc_info->depth, &wc_info->checksum, NULL,
116                                &original_repos_relpath,
117                                &original_repos_root_url, &original_uuid,
118                                &original_revision, &lock,
119                                &wc_info->recorded_size,
120                                &wc_info->recorded_time,
121                                &wc_info->changelist,
122                                &conflicted, &op_root, NULL, NULL,
123                                &have_base, &have_more_work, NULL,
124                                db, local_abspath,
125                                result_pool, scratch_pool));
126
127   if (original_repos_root_url != NULL)
128     {
129       tmpinfo->repos_root_URL = original_repos_root_url;
130       tmpinfo->repos_UUID = original_uuid;
131     }
132
133   if (status == svn_wc__db_status_added)
134     {
135       /* ### We should also just be fetching the true BASE revision
136          ### here, which means copied items would also not have a
137          ### revision to display.  But WC-1 wants to show the revision of
138          ### copy targets as the copyfrom-rev.  *sigh* */
139
140       if (original_repos_relpath)
141         {
142           /* Root or child of copy */
143           tmpinfo->rev = original_revision;
144           repos_relpath = original_repos_relpath;
145
146           if (op_root)
147             {
148               svn_error_t *err;
149               wc_info->copyfrom_url =
150                     svn_path_url_add_component2(tmpinfo->repos_root_URL,
151                                                 original_repos_relpath,
152                                                 result_pool);
153
154               wc_info->copyfrom_rev = original_revision;
155
156               err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath,
157                                           NULL, NULL, NULL,
158                                           db, local_abspath,
159                                           result_pool, scratch_pool);
160
161               if (err)
162                 {
163                    if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
164                       return svn_error_trace(err);
165                    svn_error_clear(err);
166                    wc_info->moved_from_abspath = NULL;
167                 }
168             }
169         }
170       else if (op_root)
171         {
172           /* Local addition */
173           SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath,
174                                            &tmpinfo->repos_root_URL,
175                                            &tmpinfo->repos_UUID,
176                                            NULL, NULL, NULL, NULL,
177                                            db, local_abspath,
178                                            result_pool, scratch_pool));
179
180           if (have_base)
181             SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL,
182                                              NULL, NULL, NULL, NULL, NULL,
183                                              NULL, NULL, NULL, NULL, NULL,
184                                              NULL, NULL,
185                                              db, local_abspath,
186                                              scratch_pool, scratch_pool));
187         }
188       else
189         {
190           /* Child of copy. ### Not WC-NG like */
191           SVN_ERR(svn_wc__internal_get_origin(NULL, &tmpinfo->rev,
192                                               &repos_relpath,
193                                               &tmpinfo->repos_root_URL,
194                                               &tmpinfo->repos_UUID, NULL,
195                                               db, local_abspath, TRUE,
196                                               result_pool, scratch_pool));
197         }
198
199       /* ### We should be able to avoid both these calls with the information
200          from read_info() in most cases */
201       if (! op_root)
202         wc_info->schedule = svn_wc_schedule_normal;
203       else if (! have_more_work && ! have_base)
204         wc_info->schedule = svn_wc_schedule_add;
205       else
206         {
207           svn_wc__db_status_t below_working;
208           svn_boolean_t have_work;
209
210           SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
211                                                 &below_working,
212                                                 db, local_abspath,
213                                                 scratch_pool));
214
215           /* If the node is not present or deleted (read: not present
216              in working), then the node is not a replacement */
217           if (below_working != svn_wc__db_status_not_present
218               && below_working != svn_wc__db_status_deleted)
219             {
220               wc_info->schedule = svn_wc_schedule_replace;
221             }
222           else
223             wc_info->schedule = svn_wc_schedule_add;
224         }
225       SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath,
226                                 result_pool, scratch_pool));
227     }
228   else if (status == svn_wc__db_status_deleted)
229     {
230       const char *work_del_abspath;
231
232       SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL,
233                                             &tmpinfo->last_changed_rev,
234                                             &tmpinfo->last_changed_date,
235                                             &tmpinfo->last_changed_author,
236                                             &wc_info->depth,
237                                             &wc_info->checksum,
238                                             NULL, NULL, NULL,
239                                             db, local_abspath,
240                                             result_pool, scratch_pool));
241
242       /* And now fetch the url and revision of what will be deleted */
243       SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath,
244                                        &work_del_abspath, NULL,
245                                        db, local_abspath,
246                                        scratch_pool, scratch_pool));
247       if (work_del_abspath != NULL)
248         {
249           /* This is a deletion within a copied subtree. Get the copied-from
250            * revision. */
251           const char *added_abspath = svn_dirent_dirname(work_del_abspath,
252                                                          scratch_pool);
253
254           SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath,
255                                            &tmpinfo->repos_root_URL,
256                                            &tmpinfo->repos_UUID,
257                                            NULL, NULL, NULL,
258                                            &tmpinfo->rev,
259                                            db, added_abspath,
260                                            result_pool, scratch_pool));
261
262           tmpinfo->URL = svn_path_url_add_component2(
263                               tmpinfo->repos_root_URL,
264                               svn_relpath_join(repos_relpath,
265                                     svn_dirent_skip_ancestor(added_abspath,
266                                                              local_abspath),
267                                     scratch_pool),
268                               result_pool);
269         }
270       else
271         {
272           SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev,
273                                            &repos_relpath,
274                                            &tmpinfo->repos_root_URL,
275                                            &tmpinfo->repos_UUID, NULL, NULL,
276                                            NULL, NULL, NULL, NULL,
277                                            NULL, NULL, NULL, NULL,
278                                            db, local_abspath,
279                                            result_pool, scratch_pool));
280
281           tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
282                                                      repos_relpath,
283                                                      result_pool);
284         }
285
286       wc_info->schedule = svn_wc_schedule_delete;
287     }
288   else if (status == svn_wc__db_status_not_present
289            || status == svn_wc__db_status_server_excluded)
290     {
291       *info = NULL;
292       return SVN_NO_ERROR;
293     }
294   else
295     {
296       /* Just a BASE node. We have all the info we need */
297       tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
298                                                  repos_relpath,
299                                                  result_pool);
300       wc_info->schedule = svn_wc_schedule_normal;
301     }
302
303   if (status == svn_wc__db_status_excluded)
304     tmpinfo->wc_info->depth = svn_depth_exclude;
305
306   /* A default */
307   tmpinfo->size = SVN_INVALID_FILESIZE;
308
309   SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db,
310                                 local_abspath, result_pool, scratch_pool));
311
312   if (conflicted)
313     SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db,
314                                    local_abspath,
315                                    TRUE /* ### create tempfiles */,
316                                    result_pool, scratch_pool));
317   else
318     wc_info->conflicts = NULL;
319
320   /* lock stuff */
321   if (lock != NULL)
322     {
323       tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock)));
324       tmpinfo->lock->token         = lock->token;
325       tmpinfo->lock->owner         = lock->owner;
326       tmpinfo->lock->comment       = lock->comment;
327       tmpinfo->lock->creation_date = lock->date;
328     }
329
330   *info = tmpinfo;
331   return SVN_NO_ERROR;
332 }
333
334
335 /* Set *INFO to a new struct with minimal content, to be
336    used in reporting info for unversioned tree conflict victims. */
337 /* ### Some fields we could fill out based on the parent dir's entry
338        or by looking at an obstructing item. */
339 static svn_error_t *
340 build_info_for_unversioned(svn_wc__info2_t **info,
341                            apr_pool_t *pool)
342 {
343   svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
344   svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info));
345
346   tmpinfo->URL                  = NULL;
347   tmpinfo->repos_UUID           = NULL;
348   tmpinfo->repos_root_URL       = NULL;
349   tmpinfo->rev                  = SVN_INVALID_REVNUM;
350   tmpinfo->kind                 = svn_node_none;
351   tmpinfo->size                 = SVN_INVALID_FILESIZE;
352   tmpinfo->last_changed_rev     = SVN_INVALID_REVNUM;
353   tmpinfo->last_changed_date    = 0;
354   tmpinfo->last_changed_author  = NULL;
355   tmpinfo->lock                 = NULL;
356
357   tmpinfo->wc_info = wc_info;
358
359   wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
360   wc_info->depth = svn_depth_unknown;
361   wc_info->recorded_size = SVN_INVALID_FILESIZE;
362
363   *info = tmpinfo;
364   return SVN_NO_ERROR;
365 }
366
367 /* Callback and baton for crawl_entries() walk over entries files. */
368 struct found_entry_baton
369 {
370   svn_wc__info_receiver2_t receiver;
371   void *receiver_baton;
372   svn_wc__db_t *db;
373   svn_boolean_t actual_only;
374   svn_boolean_t first;
375   /* The set of tree conflicts that have been found but not (yet) visited by
376    * the tree walker.  Map of abspath -> svn_wc_conflict_description2_t. */
377   apr_hash_t *tree_conflicts;
378   apr_pool_t *pool;
379 };
380
381 /* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it
382  * info about the path LOCAL_ABSPATH.
383  * An svn_wc__node_found_func_t callback function. */
384 static svn_error_t *
385 info_found_node_callback(const char *local_abspath,
386                          svn_node_kind_t kind,
387                          void *walk_baton,
388                          apr_pool_t *scratch_pool)
389 {
390   struct found_entry_baton *fe_baton = walk_baton;
391   svn_wc__info2_t *info;
392
393   SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath,
394                                kind, scratch_pool, scratch_pool));
395
396   if (info == NULL)
397     {
398       if (!fe_baton->first)
399         return SVN_NO_ERROR; /* not present or server excluded descendant */
400
401       /* If the info root is not found, that is an error */
402       return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
403                                _("The node '%s' was not found."),
404                                svn_dirent_local_style(local_abspath,
405                                                       scratch_pool));
406     }
407
408   fe_baton->first = FALSE;
409
410   SVN_ERR_ASSERT(info->wc_info != NULL);
411   SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath,
412                              info, scratch_pool));
413
414   /* If this node is a versioned directory, make a note of any tree conflicts
415    * on all immediate children.  Some of these may be visited later in this
416    * walk, at which point they will be removed from the list, while any that
417    * are not visited will remain in the list. */
418   if (fe_baton->actual_only && kind == svn_node_dir)
419     {
420       const apr_array_header_t *victims;
421       int i;
422
423       SVN_ERR(svn_wc__db_read_conflict_victims(&victims,
424                                                fe_baton->db, local_abspath,
425                                                scratch_pool, scratch_pool));
426
427       for (i = 0; i < victims->nelts; i++)
428         {
429           const char *this_basename = APR_ARRAY_IDX(victims, i, const char *);
430
431           svn_hash_sets(fe_baton->tree_conflicts,
432                         svn_dirent_join(local_abspath, this_basename,
433                                         fe_baton->pool),
434                         "");
435         }
436     }
437
438   /* Delete this path which we are currently visiting from the list of tree
439    * conflicts.  This relies on the walker visiting a directory before visiting
440    * its children. */
441   svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL);
442
443   return SVN_NO_ERROR;
444 }
445
446
447 /* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH,
448  * would include the path CHILD_ABSPATH of kind CHILD_KIND. */
449 static svn_boolean_t
450 depth_includes(const char *root_abspath,
451                svn_depth_t depth,
452                const char *child_abspath,
453                svn_node_kind_t child_kind,
454                apr_pool_t *scratch_pool)
455 {
456   const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool);
457
458   return (depth == svn_depth_infinity
459           || ((depth == svn_depth_immediates
460                || (depth == svn_depth_files && child_kind == svn_node_file))
461               && strcmp(root_abspath, parent_abspath) == 0)
462           || strcmp(root_abspath, child_abspath) == 0);
463 }
464
465
466 svn_error_t *
467 svn_wc__get_info(svn_wc_context_t *wc_ctx,
468                  const char *local_abspath,
469                  svn_depth_t depth,
470                  svn_boolean_t fetch_excluded,
471                  svn_boolean_t fetch_actual_only,
472                  const apr_array_header_t *changelist_filter,
473                  svn_wc__info_receiver2_t receiver,
474                  void *receiver_baton,
475                  svn_cancel_func_t cancel_func,
476                  void *cancel_baton,
477                  apr_pool_t *scratch_pool)
478 {
479   struct found_entry_baton fe_baton;
480   svn_error_t *err;
481   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
482   apr_hash_index_t *hi;
483   const char *repos_root_url = NULL;
484   const char *repos_uuid = NULL;
485
486   fe_baton.receiver = receiver;
487   fe_baton.receiver_baton = receiver_baton;
488   fe_baton.db = wc_ctx->db;
489   fe_baton.actual_only = fetch_actual_only;
490   fe_baton.first = TRUE;
491   fe_baton.tree_conflicts = apr_hash_make(scratch_pool);
492   fe_baton.pool = scratch_pool;
493
494   err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
495                                        fetch_excluded,
496                                        changelist_filter,
497                                        info_found_node_callback,
498                                        &fe_baton, depth,
499                                        cancel_func, cancel_baton,
500                                        iterpool);
501
502   /* If the target root node is not present, svn_wc__internal_walk_children()
503      returns a PATH_NOT_FOUND error and doesn't call the callback.  If there
504      is a tree conflict on this node, that is not an error. */
505   if (fe_baton.first /* not visited by walk_children */
506       && fetch_actual_only
507       && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
508     {
509       svn_boolean_t tree_conflicted;
510       svn_error_t *err2;
511
512       err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted,
513                                            wc_ctx->db, local_abspath,
514                                            iterpool);
515
516       if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND))
517         {
518           svn_error_clear(err2);
519           return svn_error_trace(err);
520         }
521       else if (err2 || !tree_conflicted)
522         return svn_error_compose_create(err, err2);
523
524       svn_error_clear(err);
525
526       svn_hash_sets(fe_baton.tree_conflicts, local_abspath, "");
527     }
528   else
529     SVN_ERR(err);
530
531   /* If there are any tree conflicts that we have found but have not reported,
532    * send a minimal info struct for each one now. */
533   for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi;
534        hi = apr_hash_next(hi))
535     {
536       const char *this_abspath = svn__apr_hash_index_key(hi);
537       const svn_wc_conflict_description2_t *tree_conflict;
538       svn_wc__info2_t *info;
539
540       svn_pool_clear(iterpool);
541
542       SVN_ERR(build_info_for_unversioned(&info, iterpool));
543
544       if (!repos_root_url)
545         {
546           SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL,
547                                                   &repos_root_url,
548                                                   &repos_uuid,
549                                                   wc_ctx->db,
550                                                   svn_dirent_dirname(
551                                                             local_abspath,
552                                                             iterpool),
553                                                   scratch_pool,
554                                                   iterpool));
555         }
556
557       info->repos_root_URL = repos_root_url;
558       info->repos_UUID = repos_uuid;
559
560       SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts,
561                                      wc_ctx->db, this_abspath,
562                                      TRUE /* ### create tempfiles */,
563                                      iterpool, iterpool));
564
565       if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts)
566         continue;
567
568       tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0,
569                                     svn_wc_conflict_description2_t *);
570
571       if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath,
572                           tree_conflict->node_kind, iterpool))
573         continue;
574
575       SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool));
576     }
577   svn_pool_destroy(iterpool);
578
579   return SVN_NO_ERROR;
580 }