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
37svn_wc_info_t *
38svn_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. */
78static svn_error_t *
79build_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. */
339static svn_error_t *
340build_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. */
368struct 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. */
384static svn_error_t *
385info_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. */
449static svn_boolean_t
450depth_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
466svn_error_t *
467svn_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                                                            this_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}
581