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