info.c revision 299742
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
145          if (op_root)
146            {
147              svn_error_t *err;
148              wc_info->copyfrom_url =
149                    svn_path_url_add_component2(tmpinfo->repos_root_URL,
150                                                original_repos_relpath,
151                                                result_pool);
152
153              wc_info->copyfrom_rev = original_revision;
154
155              err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath,
156                                          NULL, NULL, NULL,
157                                          db, local_abspath,
158                                          result_pool, scratch_pool);
159
160              if (err)
161                {
162                   if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
163                      return svn_error_trace(err);
164                   svn_error_clear(err);
165                   wc_info->moved_from_abspath = NULL;
166                }
167            }
168        }
169
170      /* ### We should be able to avoid both these calls with the information
171         from read_info() in most cases */
172      if (! op_root)
173        wc_info->schedule = svn_wc_schedule_normal;
174      else if (! have_more_work && ! have_base)
175        wc_info->schedule = svn_wc_schedule_add;
176      else
177        {
178          svn_wc__db_status_t below_working;
179          svn_boolean_t have_work;
180
181          SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
182                                                &below_working,
183                                                db, local_abspath,
184                                                scratch_pool));
185
186          /* If the node is not present or deleted (read: not present
187             in working), then the node is not a replacement */
188          if (below_working != svn_wc__db_status_not_present
189              && below_working != svn_wc__db_status_deleted)
190            {
191              wc_info->schedule = svn_wc_schedule_replace;
192            }
193          else
194            wc_info->schedule = svn_wc_schedule_add;
195        }
196      SVN_ERR(svn_wc__db_read_repos_info(NULL, &repos_relpath,
197                                         &tmpinfo->repos_root_URL,
198                                         &tmpinfo->repos_UUID,
199                                         db, local_abspath,
200                                         result_pool, scratch_pool));
201
202      tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
203                                                 repos_relpath, result_pool);
204    }
205  else if (status == svn_wc__db_status_deleted)
206    {
207      svn_wc__db_status_t w_status;
208
209      SVN_ERR(svn_wc__db_read_pristine_info(&w_status, &tmpinfo->kind,
210                                            &tmpinfo->last_changed_rev,
211                                            &tmpinfo->last_changed_date,
212                                            &tmpinfo->last_changed_author,
213                                            &wc_info->depth,
214                                            &wc_info->checksum,
215                                            NULL, NULL, NULL,
216                                            db, local_abspath,
217                                            result_pool, scratch_pool));
218
219      if (w_status == svn_wc__db_status_deleted)
220        {
221          /* We have a working not-present status. We don't know anything
222             about this node, but it *is visible* in STATUS.
223
224             Let's tell that it is excluded */
225
226          wc_info->depth = svn_depth_exclude;
227          tmpinfo->kind = svn_node_unknown;
228        }
229
230      /* And now fetch the url and revision of what will be deleted */
231      SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath,
232                                       NULL, NULL,
233                                       db, local_abspath,
234                                       scratch_pool, scratch_pool));
235
236      SVN_ERR(svn_wc__db_read_repos_info(&tmpinfo->rev, &repos_relpath,
237                                         &tmpinfo->repos_root_URL,
238                                         &tmpinfo->repos_UUID,
239                                         db, local_abspath,
240                                         result_pool, scratch_pool));
241
242      wc_info->schedule = svn_wc_schedule_delete;
243      tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
244                                                 repos_relpath, result_pool);
245    }
246  else if (status == svn_wc__db_status_not_present
247           || status == svn_wc__db_status_server_excluded)
248    {
249      *info = NULL;
250      return SVN_NO_ERROR;
251    }
252  else if (status == svn_wc__db_status_excluded && !repos_relpath)
253    {
254      /* We have a WORKING exclude. Avoid segfault on no repos info */
255
256      SVN_ERR(svn_wc__db_read_repos_info(NULL, &repos_relpath,
257                                         &tmpinfo->repos_root_URL,
258                                         &tmpinfo->repos_UUID,
259                                         db, local_abspath,
260                                         result_pool, scratch_pool));
261
262      wc_info->schedule = svn_wc_schedule_normal;
263      tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
264                                                 repos_relpath, result_pool);
265      tmpinfo->wc_info->depth = svn_depth_exclude;
266    }
267  else
268    {
269      /* Just a BASE node. We have all the info we need */
270      tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
271                                                 repos_relpath,
272                                                 result_pool);
273      wc_info->schedule = svn_wc_schedule_normal;
274
275      if (status == svn_wc__db_status_excluded)
276        wc_info->depth = svn_depth_exclude;
277    }
278
279  /* A default */
280  tmpinfo->size = SVN_INVALID_FILESIZE;
281
282  SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db,
283                                local_abspath, result_pool, scratch_pool));
284
285  if (conflicted)
286    SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, NULL,
287                                   db, local_abspath,
288                                   FALSE /* create tempfiles */,
289                                   FALSE /* only tree conflicts */,
290                                   result_pool, scratch_pool));
291  else
292    wc_info->conflicts = NULL;
293
294  /* lock stuff */
295  if (lock != NULL)
296    {
297      tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock)));
298      tmpinfo->lock->token         = lock->token;
299      tmpinfo->lock->owner         = lock->owner;
300      tmpinfo->lock->comment       = lock->comment;
301      tmpinfo->lock->creation_date = lock->date;
302    }
303
304  *info = tmpinfo;
305  return SVN_NO_ERROR;
306}
307
308
309/* Set *INFO to a new struct with minimal content, to be
310   used in reporting info for unversioned tree conflict victims. */
311/* ### Some fields we could fill out based on the parent dir's entry
312       or by looking at an obstructing item. */
313static svn_error_t *
314build_info_for_unversioned(svn_wc__info2_t **info,
315                           apr_pool_t *pool)
316{
317  svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
318  svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info));
319
320  tmpinfo->URL                  = NULL;
321  tmpinfo->repos_UUID           = NULL;
322  tmpinfo->repos_root_URL       = NULL;
323  tmpinfo->rev                  = SVN_INVALID_REVNUM;
324  tmpinfo->kind                 = svn_node_none;
325  tmpinfo->size                 = SVN_INVALID_FILESIZE;
326  tmpinfo->last_changed_rev     = SVN_INVALID_REVNUM;
327  tmpinfo->last_changed_date    = 0;
328  tmpinfo->last_changed_author  = NULL;
329  tmpinfo->lock                 = NULL;
330
331  tmpinfo->wc_info = wc_info;
332
333  wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
334  wc_info->depth = svn_depth_unknown;
335  wc_info->recorded_size = SVN_INVALID_FILESIZE;
336
337  *info = tmpinfo;
338  return SVN_NO_ERROR;
339}
340
341/* Callback and baton for crawl_entries() walk over entries files. */
342struct found_entry_baton
343{
344  svn_wc__info_receiver2_t receiver;
345  void *receiver_baton;
346  svn_wc__db_t *db;
347  svn_boolean_t actual_only;
348  svn_boolean_t first;
349  /* The set of tree conflicts that have been found but not (yet) visited by
350   * the tree walker.  Map of abspath -> empty string. */
351  apr_hash_t *tree_conflicts;
352  apr_pool_t *pool;
353};
354
355/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it
356 * info about the path LOCAL_ABSPATH.
357 * An svn_wc__node_found_func_t callback function. */
358static svn_error_t *
359info_found_node_callback(const char *local_abspath,
360                         svn_node_kind_t kind,
361                         void *walk_baton,
362                         apr_pool_t *scratch_pool)
363{
364  struct found_entry_baton *fe_baton = walk_baton;
365  svn_wc__info2_t *info;
366
367  SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath,
368                               kind, scratch_pool, scratch_pool));
369
370  if (info == NULL)
371    {
372      if (!fe_baton->first)
373        return SVN_NO_ERROR; /* not present or server excluded descendant */
374
375      /* If the info root is not found, that is an error */
376      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
377                               _("The node '%s' was not found."),
378                               svn_dirent_local_style(local_abspath,
379                                                      scratch_pool));
380    }
381
382  fe_baton->first = FALSE;
383
384  SVN_ERR_ASSERT(info->wc_info != NULL);
385  SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath,
386                             info, scratch_pool));
387
388  /* If this node is a versioned directory, make a note of any tree conflicts
389   * on all immediate children.  Some of these may be visited later in this
390   * walk, at which point they will be removed from the list, while any that
391   * are not visited will remain in the list. */
392  if (fe_baton->actual_only && kind == svn_node_dir)
393    {
394      const apr_array_header_t *victims;
395      int i;
396
397      SVN_ERR(svn_wc__db_read_conflict_victims(&victims,
398                                               fe_baton->db, local_abspath,
399                                               scratch_pool, scratch_pool));
400
401      for (i = 0; i < victims->nelts; i++)
402        {
403          const char *this_basename = APR_ARRAY_IDX(victims, i, const char *);
404
405          svn_hash_sets(fe_baton->tree_conflicts,
406                        svn_dirent_join(local_abspath, this_basename,
407                                        fe_baton->pool),
408                        "");
409        }
410    }
411
412  /* Delete this path which we are currently visiting from the list of tree
413   * conflicts.  This relies on the walker visiting a directory before visiting
414   * its children. */
415  svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL);
416
417  return SVN_NO_ERROR;
418}
419
420
421/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH,
422 * would include the path CHILD_ABSPATH of kind CHILD_KIND. */
423static svn_boolean_t
424depth_includes(const char *root_abspath,
425               svn_depth_t depth,
426               const char *child_abspath,
427               svn_node_kind_t child_kind,
428               apr_pool_t *scratch_pool)
429{
430  const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool);
431
432  return (depth == svn_depth_infinity
433          || ((depth == svn_depth_immediates
434               || (depth == svn_depth_files && child_kind == svn_node_file))
435              && strcmp(root_abspath, parent_abspath) == 0)
436          || strcmp(root_abspath, child_abspath) == 0);
437}
438
439
440svn_error_t *
441svn_wc__get_info(svn_wc_context_t *wc_ctx,
442                 const char *local_abspath,
443                 svn_depth_t depth,
444                 svn_boolean_t fetch_excluded,
445                 svn_boolean_t fetch_actual_only,
446                 const apr_array_header_t *changelist_filter,
447                 svn_wc__info_receiver2_t receiver,
448                 void *receiver_baton,
449                 svn_cancel_func_t cancel_func,
450                 void *cancel_baton,
451                 apr_pool_t *scratch_pool)
452{
453  struct found_entry_baton fe_baton;
454  svn_error_t *err;
455  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
456  apr_hash_index_t *hi;
457  const char *repos_root_url = NULL;
458  const char *repos_uuid = NULL;
459
460  fe_baton.receiver = receiver;
461  fe_baton.receiver_baton = receiver_baton;
462  fe_baton.db = wc_ctx->db;
463  fe_baton.actual_only = fetch_actual_only;
464  fe_baton.first = TRUE;
465  fe_baton.tree_conflicts = apr_hash_make(scratch_pool);
466  fe_baton.pool = scratch_pool;
467
468  err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
469                                       fetch_excluded,
470                                       changelist_filter,
471                                       info_found_node_callback,
472                                       &fe_baton, depth,
473                                       cancel_func, cancel_baton,
474                                       iterpool);
475
476  /* If the target root node is not present, svn_wc__internal_walk_children()
477     returns a PATH_NOT_FOUND error and doesn't call the callback.  If there
478     is a tree conflict on this node, that is not an error. */
479  if (fe_baton.first /* not visited by walk_children */
480      && fetch_actual_only
481      && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
482    {
483      svn_boolean_t tree_conflicted;
484      svn_error_t *err2;
485
486      err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted,
487                                           wc_ctx->db, local_abspath,
488                                           iterpool);
489
490      if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND))
491        {
492          svn_error_clear(err2);
493          return svn_error_trace(err);
494        }
495      else if (err2 || !tree_conflicted)
496        return svn_error_compose_create(err, err2);
497
498      svn_error_clear(err);
499
500      svn_hash_sets(fe_baton.tree_conflicts, local_abspath, "");
501    }
502  else
503    SVN_ERR(err);
504
505  /* If there are any tree conflicts that we have found but have not reported,
506   * send a minimal info struct for each one now. */
507  for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi;
508       hi = apr_hash_next(hi))
509    {
510      const char *this_abspath = apr_hash_this_key(hi);
511      const svn_wc_conflict_description2_t *tree_conflict;
512      svn_wc__info2_t *info;
513      const apr_array_header_t *conflicts;
514
515      svn_pool_clear(iterpool);
516
517      SVN_ERR(build_info_for_unversioned(&info, iterpool));
518
519      if (!repos_root_url)
520        {
521          SVN_ERR(svn_wc__db_read_repos_info(NULL, NULL,
522                                             &repos_root_url,
523                                             &repos_uuid,
524                                             wc_ctx->db,
525                                             svn_dirent_dirname(
526                                                            this_abspath,
527                                                            iterpool),
528                                             scratch_pool, iterpool));
529        }
530
531      info->repos_root_URL = repos_root_url;
532      info->repos_UUID = repos_uuid;
533
534      SVN_ERR(svn_wc__read_conflicts(&conflicts, NULL,
535                                     wc_ctx->db, this_abspath,
536                                     FALSE /* create tempfiles */,
537                                     FALSE /* only tree conflicts */,
538                                     iterpool, iterpool));
539      if (! conflicts || ! conflicts->nelts)
540        continue;
541
542      tree_conflict = APR_ARRAY_IDX(conflicts, 0,
543                                    const svn_wc_conflict_description2_t *);
544
545      if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath,
546                          tree_conflict->node_kind, iterpool))
547        continue;
548
549      info->wc_info->conflicts = conflicts;
550      SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool));
551    }
552  svn_pool_destroy(iterpool);
553
554  return SVN_NO_ERROR;
555}
556