1/*
2 * status.c:  return the status of a working copy dirent
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/* We define this here to remove any further warnings about the usage of
27   experimental functions in this file. */
28#define SVN_EXPERIMENTAL
29
30
31/*** Includes. ***/
32#include <apr_strings.h>
33#include <apr_pools.h>
34
35#include "svn_private_config.h"
36#include "svn_pools.h"
37#include "svn_sorts.h"
38#include "client.h"
39
40#include "svn_path.h"
41#include "svn_dirent_uri.h"
42#include "svn_delta.h"
43#include "svn_client.h"
44#include "svn_error.h"
45#include "svn_hash.h"
46
47#include "private/svn_client_shelf.h"
48#include "private/svn_client_private.h"
49#include "private/svn_sorts_private.h"
50#include "private/svn_wc_private.h"
51
52
53/*** Getting update information ***/
54
55/* Baton for tweak_status.  It wraps a bit of extra functionality
56   around the received status func/baton, so we can remember if the
57   target was deleted in HEAD and tweak incoming status structures
58   accordingly. */
59struct status_baton
60{
61  svn_boolean_t deleted_in_repos;             /* target is deleted in repos */
62  apr_hash_t *changelist_hash;                /* keys are changelist names */
63  svn_client_status_func_t real_status_func;  /* real status function */
64  void *real_status_baton;                    /* real status baton */
65  const char *anchor_abspath;                 /* Absolute path of anchor */
66  const char *anchor_relpath;                 /* Relative path of anchor */
67  svn_wc_context_t *wc_ctx;                   /* A working copy context. */
68};
69
70/* A status callback function which wraps the *real* status
71   function/baton.   This sucker takes care of any status tweaks we
72   need to make (such as noting that the target of the status is
73   missing from HEAD in the repository).
74
75   This implements the 'svn_wc_status_func4_t' function type.  */
76static svn_error_t *
77tweak_status(void *baton,
78             const char *local_abspath,
79             const svn_wc_status3_t *status,
80             apr_pool_t *scratch_pool)
81{
82  struct status_baton *sb = baton;
83  const char *path = local_abspath;
84  svn_client_status_t *cst;
85
86  if (sb->anchor_abspath)
87    path = svn_dirent_join(sb->anchor_relpath,
88                           svn_dirent_skip_ancestor(sb->anchor_abspath, path),
89                           scratch_pool);
90
91  /* If the status item has an entry, but doesn't belong to one of the
92     changelists our caller is interested in, we filter out this status
93     transmission.  */
94  if (sb->changelist_hash
95      && (! status->changelist
96          || ! svn_hash_gets(sb->changelist_hash, status->changelist)))
97    {
98      return SVN_NO_ERROR;
99    }
100
101  /* If we know that the target was deleted in HEAD of the repository,
102     we need to note that fact in all the status structures that come
103     through here. */
104  if (sb->deleted_in_repos)
105    {
106      svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
107      new_status->repos_node_status = svn_wc_status_deleted;
108      status = new_status;
109    }
110
111  SVN_ERR(svn_client__create_status(&cst, sb->wc_ctx, local_abspath, status,
112                                    scratch_pool, scratch_pool));
113
114  /* Call the real status function/baton. */
115  return sb->real_status_func(sb->real_status_baton, path, cst,
116                              scratch_pool);
117}
118
119/* A baton for our reporter that is used to collect locks. */
120typedef struct report_baton_t {
121  const svn_ra_reporter3_t* wrapped_reporter;
122  void *wrapped_report_baton;
123  /* The common ancestor URL of all paths included in the report. */
124  char *ancestor;
125  void *set_locks_baton;
126  svn_depth_t depth;
127  svn_client_ctx_t *ctx;
128  /* Pool to store locks in. */
129  apr_pool_t *pool;
130} report_baton_t;
131
132/* Implements svn_ra_reporter3_t->set_path. */
133static svn_error_t *
134reporter_set_path(void *report_baton, const char *path,
135                  svn_revnum_t revision, svn_depth_t depth,
136                  svn_boolean_t start_empty, const char *lock_token,
137                  apr_pool_t *pool)
138{
139  report_baton_t *rb = report_baton;
140
141  return rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path,
142                                        revision, depth, start_empty,
143                                        lock_token, pool);
144}
145
146/* Implements svn_ra_reporter3_t->delete_path. */
147static svn_error_t *
148reporter_delete_path(void *report_baton, const char *path, apr_pool_t *pool)
149{
150  report_baton_t *rb = report_baton;
151
152  return rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, path,
153                                           pool);
154}
155
156/* Implements svn_ra_reporter3_t->link_path. */
157static svn_error_t *
158reporter_link_path(void *report_baton, const char *path, const char *url,
159                   svn_revnum_t revision, svn_depth_t depth,
160                   svn_boolean_t start_empty,
161                   const char *lock_token, apr_pool_t *pool)
162{
163  report_baton_t *rb = report_baton;
164
165  if (!svn_uri__is_ancestor(rb->ancestor, url))
166    {
167      const char *ancestor;
168
169      ancestor = svn_uri_get_longest_ancestor(url, rb->ancestor, pool);
170
171      /* If we got a shorter ancestor, truncate our current ancestor.
172         Note that svn_uri_get_longest_ancestor will allocate its return
173         value even if it identical to one of its arguments. */
174
175      rb->ancestor[strlen(ancestor)] = '\0';
176      rb->depth = svn_depth_infinity;
177    }
178
179  return rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url,
180                                         revision, depth, start_empty,
181                                         lock_token, pool);
182}
183
184/* Implements svn_ra_reporter3_t->finish_report. */
185static svn_error_t *
186reporter_finish_report(void *report_baton, apr_pool_t *pool)
187{
188  report_baton_t *rb = report_baton;
189  svn_ra_session_t *ras;
190  apr_hash_t *locks;
191  const char *repos_root;
192  apr_pool_t *subpool = svn_pool_create(pool);
193  svn_error_t *err = SVN_NO_ERROR;
194
195  /* Open an RA session to our common ancestor and grab the locks under it.
196   */
197  SVN_ERR(svn_client_open_ra_session2(&ras, rb->ancestor, NULL,
198                                      rb->ctx, subpool, subpool));
199
200  /* The locks need to live throughout the edit.  Note that if the
201     server doesn't support lock discovery, we'll just not do locky
202     stuff. */
203  err = svn_ra_get_locks2(ras, &locks, "", rb->depth, rb->pool);
204  if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
205    {
206      svn_error_clear(err);
207      err = SVN_NO_ERROR;
208      locks = apr_hash_make(rb->pool);
209    }
210  SVN_ERR(err);
211
212  SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool));
213
214  /* Close the RA session. */
215  svn_pool_destroy(subpool);
216
217  SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks,
218                                        repos_root, rb->pool));
219
220  return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool);
221}
222
223/* Implements svn_ra_reporter3_t->abort_report. */
224static svn_error_t *
225reporter_abort_report(void *report_baton, apr_pool_t *pool)
226{
227  report_baton_t *rb = report_baton;
228
229  return rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool);
230}
231
232/* A reporter that keeps track of the common URL ancestor of all paths in
233   the WC and fetches repository locks for all paths under this ancestor. */
234static svn_ra_reporter3_t lock_fetch_reporter = {
235  reporter_set_path,
236  reporter_delete_path,
237  reporter_link_path,
238  reporter_finish_report,
239  reporter_abort_report
240};
241
242/* Perform status operations on each external in EXTERNAL_MAP, a const char *
243   local_abspath of all externals mapping to the const char* defining_abspath.
244   All other options are the same as those passed to svn_client_status().
245
246   If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide
247   properly formatted relative paths */
248static svn_error_t *
249do_external_status(svn_client_ctx_t *ctx,
250                   apr_hash_t *external_map,
251                   svn_depth_t depth,
252                   svn_boolean_t get_all,
253                   svn_boolean_t check_out_of_date,
254                   svn_boolean_t check_working_copy,
255                   svn_boolean_t no_ignore,
256                   const apr_array_header_t *changelists,
257                   const char *anchor_abspath,
258                   const char *anchor_relpath,
259                   svn_client_status_func_t status_func,
260                   void *status_baton,
261                   apr_pool_t *scratch_pool)
262{
263  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
264  apr_array_header_t *externals;
265  int i;
266
267  externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically,
268                             scratch_pool);
269
270  /* Loop over the hash of new values (we don't care about the old
271     ones).  This is a mapping of versioned directories to property
272     values. */
273  for (i = 0; i < externals->nelts; i++)
274    {
275      svn_node_kind_t external_kind;
276      svn_sort__item_t item = APR_ARRAY_IDX(externals, i, svn_sort__item_t);
277      const char *local_abspath = item.key;
278      const char *defining_abspath = item.value;
279      svn_node_kind_t kind;
280      svn_opt_revision_t opt_rev;
281      const char *status_path;
282
283      svn_pool_clear(iterpool);
284
285      /* Obtain information on the expected external. */
286      SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL,
287                                         &opt_rev.value.number,
288                                         ctx->wc_ctx, defining_abspath,
289                                         local_abspath, FALSE,
290                                         iterpool, iterpool));
291
292      if (external_kind != svn_node_dir)
293        continue;
294
295      SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool));
296      if (kind != svn_node_dir)
297        continue;
298
299      if (SVN_IS_VALID_REVNUM(opt_rev.value.number))
300        opt_rev.kind = svn_opt_revision_number;
301      else
302        opt_rev.kind = svn_opt_revision_unspecified;
303
304      /* Tell the client we're starting an external status set. */
305      if (ctx->notify_func2)
306        ctx->notify_func2(
307               ctx->notify_baton2,
308               svn_wc_create_notify(local_abspath,
309                                    svn_wc_notify_status_external,
310                                    iterpool), iterpool);
311
312      status_path = local_abspath;
313      if (anchor_abspath)
314        {
315          status_path = svn_dirent_join(anchor_relpath,
316                           svn_dirent_skip_ancestor(anchor_abspath,
317                                                    status_path),
318                           iterpool);
319        }
320
321      /* And then do the status. */
322      SVN_ERR(svn_client_status6(NULL, ctx, status_path, &opt_rev, depth,
323                                 get_all, check_out_of_date,
324                                 check_working_copy, no_ignore,
325                                 FALSE /* ignore_exernals */,
326                                 FALSE /* depth_as_sticky */,
327                                 changelists, status_func, status_baton,
328                                 iterpool));
329    }
330
331  /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
332  svn_pool_destroy(iterpool);
333
334  return SVN_NO_ERROR;
335}
336
337/* Run status on shelf SHELF_NAME, if it exists.
338 */
339static svn_error_t *
340shelf_status(const char *shelf_name,
341             const char *target_abspath,
342             svn_wc_status_func4_t status_func,
343             void *status_baton,
344             svn_client_ctx_t *ctx,
345             apr_pool_t *scratch_pool)
346{
347  svn_error_t *err;
348  svn_client__shelf_t *shelf;
349  svn_client__shelf_version_t *shelf_version;
350  const char *wc_relpath;
351
352  err = svn_client__shelf_open_existing(&shelf,
353                                       shelf_name, target_abspath,
354                                       ctx, scratch_pool);
355  if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
356    {
357      svn_error_clear(err);
358      return SVN_NO_ERROR;
359    }
360  else
361    SVN_ERR(err);
362
363  SVN_ERR(svn_client__shelf_version_open(&shelf_version,
364                                        shelf, shelf->max_version,
365                                        scratch_pool, scratch_pool));
366  wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath);
367  SVN_ERR(svn_client__shelf_version_status_walk(shelf_version, wc_relpath,
368                                               status_func, status_baton,
369                                               scratch_pool));
370  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
371
372  return SVN_NO_ERROR;
373}
374
375/* Run status on all shelves named in CHANGELISTS by a changelist name
376 * of the form "svn:shelf:SHELF_NAME", if they exist.
377 */
378static svn_error_t *
379shelves_status(const apr_array_header_t *changelists,
380               const char *target_abspath,
381               svn_wc_status_func4_t status_func,
382               void *status_baton,
383               svn_client_ctx_t *ctx,
384               apr_pool_t *scratch_pool)
385{
386  static const char PREFIX[] = "svn:shelf:";
387  static const int PREFIX_LEN = 10;
388  int i;
389
390  if (! changelists)
391    return SVN_NO_ERROR;
392  for (i = 0; i < changelists->nelts; i++)
393    {
394      const char *cl = APR_ARRAY_IDX(changelists, i, const char *);
395
396      if (strncmp(cl, PREFIX, PREFIX_LEN) == 0)
397        {
398          const char *shelf_name = cl + PREFIX_LEN;
399
400          SVN_ERR(shelf_status(shelf_name, target_abspath,
401                               status_func, status_baton,
402                               ctx, scratch_pool));
403        }
404    }
405
406  return SVN_NO_ERROR;
407}
408
409
410/*** Public Interface. ***/
411
412
413svn_error_t *
414svn_client_status6(svn_revnum_t *result_rev,
415                   svn_client_ctx_t *ctx,
416                   const char *path,
417                   const svn_opt_revision_t *revision,
418                   svn_depth_t depth,
419                   svn_boolean_t get_all,
420                   svn_boolean_t check_out_of_date,
421                   svn_boolean_t check_working_copy,
422                   svn_boolean_t no_ignore,
423                   svn_boolean_t ignore_externals,
424                   svn_boolean_t depth_as_sticky,
425                   const apr_array_header_t *changelists,
426                   svn_client_status_func_t status_func,
427                   void *status_baton,
428                   apr_pool_t *pool)  /* ### aka scratch_pool */
429{
430  struct status_baton sb;
431  const char *dir, *dir_abspath;
432  const char *target_abspath;
433  const char *target_basename;
434  apr_array_header_t *ignores;
435  svn_error_t *err;
436  apr_hash_t *changelist_hash = NULL;
437
438  /* Override invalid combinations of the check_out_of_date and
439     check_working_copy flags. */
440  if (!check_out_of_date)
441    check_working_copy = TRUE;
442
443  if (svn_path_is_url(path))
444    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
445                             _("'%s' is not a local path"), path);
446
447  if (changelists && changelists->nelts)
448    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool));
449
450  if (result_rev)
451    *result_rev = SVN_INVALID_REVNUM;
452
453  sb.real_status_func = status_func;
454  sb.real_status_baton = status_baton;
455  sb.deleted_in_repos = FALSE;
456  sb.changelist_hash = changelist_hash;
457  sb.wc_ctx = ctx->wc_ctx;
458
459  SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool));
460
461  if (check_out_of_date)
462    {
463      /* The status editor only works on directories, so get the ancestor
464         if necessary */
465
466      svn_node_kind_t kind;
467
468      SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
469                                TRUE, FALSE, pool));
470
471      /* Dir must be a working copy directory or the status editor fails */
472      if (kind == svn_node_dir)
473        {
474          dir_abspath = target_abspath;
475          target_basename = "";
476          dir = path;
477        }
478      else
479        {
480          dir_abspath = svn_dirent_dirname(target_abspath, pool);
481          target_basename = svn_dirent_basename(target_abspath, NULL);
482          dir = svn_dirent_dirname(path, pool);
483
484          if (kind == svn_node_file)
485            {
486              if (depth == svn_depth_empty)
487                depth = svn_depth_files;
488            }
489          else
490            {
491              err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath,
492                                      FALSE, FALSE, pool);
493
494              svn_error_clear(err);
495
496              if (err || kind != svn_node_dir)
497                {
498                  return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
499                                           _("'%s' is not a working copy"),
500                                           svn_dirent_local_style(path, pool));
501                }
502            }
503        }
504    }
505  else
506    {
507      dir = path;
508      dir_abspath = target_abspath;
509    }
510
511  if (svn_dirent_is_absolute(dir))
512    {
513      sb.anchor_abspath = NULL;
514      sb.anchor_relpath = NULL;
515    }
516  else
517    {
518      sb.anchor_abspath = dir_abspath;
519      sb.anchor_relpath = dir;
520    }
521
522  /* Get the status edit, and use our wrapping status function/baton
523     as the callback pair. */
524  SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
525
526  /* If we want to know about out-of-dateness, we crawl the working copy and
527     let the RA layer drive the editor for real.  Otherwise, we just close the
528     edit.  :-) */
529  if (check_out_of_date)
530    {
531      svn_ra_session_t *ra_session;
532      const char *URL;
533      svn_node_kind_t kind;
534      svn_boolean_t server_supports_depth;
535      const svn_delta_editor_t *editor;
536      void *edit_baton, *set_locks_baton;
537      svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
538
539      /* Get full URL from the ANCHOR. */
540      SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx,
541                                        pool, pool));
542
543      if (!URL)
544        return svn_error_createf
545          (SVN_ERR_ENTRY_MISSING_URL, NULL,
546           _("Entry '%s' has no URL"),
547           svn_dirent_local_style(dir, pool));
548
549      /* Open a repository session to the URL. */
550      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL,
551                                                   dir_abspath, NULL,
552                                                   FALSE, TRUE,
553                                                   ctx, pool, pool));
554
555      SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
556                                    SVN_RA_CAPABILITY_DEPTH, pool));
557
558      SVN_ERR(svn_wc__get_status_editor(&editor, &edit_baton, &set_locks_baton,
559                                        &edit_revision, ctx->wc_ctx,
560                                        dir_abspath, target_basename,
561                                        depth, get_all, check_working_copy,
562                                        no_ignore, depth_as_sticky,
563                                        server_supports_depth,
564                                        ignores, tweak_status, &sb,
565                                        ctx->cancel_func, ctx->cancel_baton,
566                                        pool, pool));
567
568
569      /* Verify that URL exists in HEAD.  If it doesn't, this can save
570         us a whole lot of hassle; if it does, the cost of this
571         request should be minimal compared to the size of getting
572         back the average amount of "out-of-date" information. */
573      SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM,
574                                &kind, pool));
575      if (kind == svn_node_none)
576        {
577          svn_boolean_t added;
578
579          /* Our status target does not exist in HEAD.  If we've got
580             it locally added, that's okay.  But if it was previously
581             versioned, then it must have since been deleted from the
582             repository.  (Note that "locally replaced" doesn't count
583             as "added" in this case.)  */
584          SVN_ERR(svn_wc__node_is_added(&added, ctx->wc_ctx,
585                                        dir_abspath, pool));
586          if (! added)
587            sb.deleted_in_repos = TRUE;
588
589          /* And now close the edit. */
590          SVN_ERR(editor->close_edit(edit_baton, pool));
591        }
592      else
593        {
594          svn_revnum_t revnum;
595          report_baton_t rb;
596          svn_depth_t status_depth;
597
598          if (revision->kind == svn_opt_revision_head)
599            {
600              /* Cause the revision number to be omitted from the request,
601                 which implies HEAD. */
602              revnum = SVN_INVALID_REVNUM;
603            }
604          else
605            {
606              /* Get a revision number for our status operation. */
607              SVN_ERR(svn_client__get_revision_number(&revnum, NULL,
608                                                      ctx->wc_ctx,
609                                                      target_abspath,
610                                                      ra_session, revision,
611                                                      pool));
612            }
613
614          if (depth_as_sticky || !server_supports_depth)
615            status_depth = depth;
616          else
617            status_depth = svn_depth_unknown; /* Use depth from WC */
618
619          /* Do the deed.  Let the RA layer drive the status editor. */
620          SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter,
621                                    &rb.wrapped_report_baton,
622                                    target_basename, revnum, status_depth,
623                                    editor, edit_baton, pool));
624
625          /* Init the report baton. */
626          rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */
627          rb.set_locks_baton = set_locks_baton;
628          rb.ctx = ctx;
629          rb.pool = pool;
630
631          if (depth == svn_depth_unknown)
632            rb.depth = svn_depth_infinity;
633          else
634            rb.depth = depth;
635
636          /* Drive the reporter structure, describing the revisions
637             within PATH.  When we call reporter->finish_report,
638             EDITOR will be driven to describe differences between our
639             working copy and HEAD. */
640          SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx,
641                                          target_abspath,
642                                          &lock_fetch_reporter, &rb,
643                                          FALSE /* restore_files */,
644                                          depth, (! depth_as_sticky),
645                                          (! server_supports_depth),
646                                          FALSE /* use_commit_times */,
647                                          ctx->cancel_func, ctx->cancel_baton,
648                                          NULL, NULL, pool));
649        }
650
651      if (ctx->notify_func2)
652        {
653          svn_wc_notify_t *notify
654            = svn_wc_create_notify(target_abspath,
655                                   svn_wc_notify_status_completed, pool);
656          notify->revision = edit_revision;
657          ctx->notify_func2(ctx->notify_baton2, notify, pool);
658        }
659
660      /* If the caller wants the result revision, give it to them. */
661      if (result_rev)
662        *result_rev = edit_revision;
663    }
664  else
665    {
666      SVN_ERR(shelves_status(changelists, target_abspath,
667                             tweak_status, &sb,
668                             ctx, pool));
669      err = svn_wc_walk_status(ctx->wc_ctx, target_abspath,
670                               depth, get_all, no_ignore, FALSE, ignores,
671                               tweak_status, &sb,
672                               ctx->cancel_func, ctx->cancel_baton,
673                               pool);
674
675      if (err && err->apr_err == SVN_ERR_WC_MISSING)
676        {
677          /* This error code is checked for in svn to continue after
678             this error */
679          svn_error_clear(err);
680          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
681                               _("'%s' is not a working copy"),
682                               svn_dirent_local_style(path, pool));
683        }
684
685      SVN_ERR(err);
686    }
687
688  /* We only descend into an external if depth is svn_depth_infinity or
689     svn_depth_unknown.  However, there are conceivable behaviors that
690     would involve descending under other circumstances; thus, we pass
691     depth anyway, so the code will DTRT if we change the conditional
692     in the future.
693  */
694  if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
695    {
696      apr_hash_t *external_map;
697      SVN_ERR(svn_wc__externals_defined_below(&external_map,
698                                              ctx->wc_ctx, target_abspath,
699                                              pool, pool));
700
701
702      SVN_ERR(do_external_status(ctx, external_map,
703                                 depth, get_all,
704                                 check_out_of_date, check_working_copy,
705                                 no_ignore, changelists,
706                                 sb.anchor_abspath, sb.anchor_relpath,
707                                 status_func, status_baton, pool));
708    }
709
710  return SVN_NO_ERROR;
711}
712
713svn_client_status_t *
714svn_client_status_dup(const svn_client_status_t *status,
715                      apr_pool_t *result_pool)
716{
717  svn_client_status_t *st = apr_palloc(result_pool, sizeof(*st));
718
719  *st = *status;
720
721  if (status->local_abspath)
722    st->local_abspath = apr_pstrdup(result_pool, status->local_abspath);
723
724  if (status->repos_root_url)
725    st->repos_root_url = apr_pstrdup(result_pool, status->repos_root_url);
726
727  if (status->repos_uuid)
728    st->repos_uuid = apr_pstrdup(result_pool, status->repos_uuid);
729
730  if (status->repos_relpath)
731    st->repos_relpath = apr_pstrdup(result_pool, status->repos_relpath);
732
733  if (status->changed_author)
734    st->changed_author = apr_pstrdup(result_pool, status->changed_author);
735
736  if (status->lock)
737    st->lock = svn_lock_dup(status->lock, result_pool);
738
739  if (status->changelist)
740    st->changelist = apr_pstrdup(result_pool, status->changelist);
741
742  if (status->ood_changed_author)
743    st->ood_changed_author = apr_pstrdup(result_pool, status->ood_changed_author);
744
745  if (status->repos_lock)
746    st->repos_lock = svn_lock_dup(status->repos_lock, result_pool);
747
748  if (status->backwards_compatibility_baton)
749    {
750      const svn_wc_status3_t *wc_st = status->backwards_compatibility_baton;
751
752      st->backwards_compatibility_baton = svn_wc_dup_status3(wc_st,
753                                                             result_pool);
754    }
755
756  if (status->moved_from_abspath)
757    st->moved_from_abspath =
758      apr_pstrdup(result_pool, status->moved_from_abspath);
759
760  if (status->moved_to_abspath)
761    st->moved_to_abspath = apr_pstrdup(result_pool, status->moved_to_abspath);
762
763  return st;
764}
765
766svn_error_t *
767svn_client__create_status(svn_client_status_t **cst,
768                          svn_wc_context_t *wc_ctx,
769                          const char *local_abspath,
770                          const svn_wc_status3_t *status,
771                          apr_pool_t *result_pool,
772                          apr_pool_t *scratch_pool)
773{
774  *cst = apr_pcalloc(result_pool, sizeof(**cst));
775
776  (*cst)->kind = status->kind;
777  (*cst)->local_abspath = local_abspath;
778  (*cst)->filesize = status->filesize;
779  (*cst)->versioned = status->versioned;
780
781  (*cst)->conflicted = status->conflicted;
782
783  (*cst)->node_status = status->node_status;
784  (*cst)->text_status = status->text_status;
785  (*cst)->prop_status = status->prop_status;
786
787  if (status->kind == svn_node_dir)
788    (*cst)->wc_is_locked = status->locked;
789
790  (*cst)->copied = status->copied;
791  (*cst)->revision = status->revision;
792
793  (*cst)->changed_rev = status->changed_rev;
794  (*cst)->changed_date = status->changed_date;
795  (*cst)->changed_author = status->changed_author;
796
797  (*cst)->repos_root_url = status->repos_root_url;
798  (*cst)->repos_uuid = status->repos_uuid;
799  (*cst)->repos_relpath = status->repos_relpath;
800
801  (*cst)->switched = status->switched;
802
803  (*cst)->file_external = status->file_external;
804  if (status->file_external)
805    {
806      (*cst)->switched = FALSE;
807    }
808
809  (*cst)->lock = status->lock;
810
811  (*cst)->changelist = status->changelist;
812  (*cst)->depth = status->depth;
813
814  /* Out of date information */
815  (*cst)->ood_kind = status->ood_kind;
816  (*cst)->repos_node_status = status->repos_node_status;
817  (*cst)->repos_text_status = status->repos_text_status;
818  (*cst)->repos_prop_status = status->repos_prop_status;
819  (*cst)->repos_lock = status->repos_lock;
820
821  (*cst)->ood_changed_rev = status->ood_changed_rev;
822  (*cst)->ood_changed_date = status->ood_changed_date;
823  (*cst)->ood_changed_author = status->ood_changed_author;
824
825  /* When changing the value of backwards_compatibility_baton, also
826     change its use in status4_wrapper_func in deprecated.c */
827  (*cst)->backwards_compatibility_baton = status;
828
829  if (status->versioned && status->conflicted)
830    {
831      svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
832
833      /* Note: This checks the on disk markers to automatically hide
834               text/property conflicts that are hidden by removing their
835               markers */
836      SVN_ERR(svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
837                                   &tree_conflicted, wc_ctx, local_abspath,
838                                   scratch_pool));
839
840      if (text_conflicted)
841        (*cst)->text_status = svn_wc_status_conflicted;
842
843      if (prop_conflicted)
844        (*cst)->prop_status = svn_wc_status_conflicted;
845
846      /* ### Also set this for tree_conflicts? */
847      if (text_conflicted || prop_conflicted)
848        (*cst)->node_status = svn_wc_status_conflicted;
849    }
850
851  (*cst)->moved_from_abspath = status->moved_from_abspath;
852  (*cst)->moved_to_abspath = status->moved_to_abspath;
853
854  return SVN_NO_ERROR;
855}
856
857