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