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. */
59 struct 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.  */
76 static svn_error_t *
tweak_status(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)77 tweak_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. */
120 typedef 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. */
133 static svn_error_t *
reporter_set_path(void * report_baton,const char * path,svn_revnum_t revision,svn_depth_t depth,svn_boolean_t start_empty,const char * lock_token,apr_pool_t * pool)134 reporter_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. */
147 static svn_error_t *
reporter_delete_path(void * report_baton,const char * path,apr_pool_t * pool)148 reporter_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. */
157 static svn_error_t *
reporter_link_path(void * report_baton,const char * path,const char * url,svn_revnum_t revision,svn_depth_t depth,svn_boolean_t start_empty,const char * lock_token,apr_pool_t * pool)158 reporter_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. */
185 static svn_error_t *
reporter_finish_report(void * report_baton,apr_pool_t * pool)186 reporter_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. */
224 static svn_error_t *
reporter_abort_report(void * report_baton,apr_pool_t * pool)225 reporter_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. */
234 static 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 */
248 static svn_error_t *
do_external_status(svn_client_ctx_t * ctx,apr_hash_t * external_map,svn_depth_t depth,svn_boolean_t get_all,svn_boolean_t check_out_of_date,svn_boolean_t check_working_copy,svn_boolean_t no_ignore,const apr_array_header_t * changelists,const char * anchor_abspath,const char * anchor_relpath,svn_client_status_func_t status_func,void * status_baton,apr_pool_t * scratch_pool)249 do_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  */
339 static svn_error_t *
shelf_status(const char * shelf_name,const char * target_abspath,svn_wc_status_func4_t status_func,void * status_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)340 shelf_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  */
378 static svn_error_t *
shelves_status(const apr_array_header_t * changelists,const char * target_abspath,svn_wc_status_func4_t status_func,void * status_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)379 shelves_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 
413 svn_error_t *
svn_client_status6(svn_revnum_t * result_rev,svn_client_ctx_t * ctx,const char * path,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t get_all,svn_boolean_t check_out_of_date,svn_boolean_t check_working_copy,svn_boolean_t no_ignore,svn_boolean_t ignore_externals,svn_boolean_t depth_as_sticky,const apr_array_header_t * changelists,svn_client_status_func_t status_func,void * status_baton,apr_pool_t * pool)414 svn_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 
713 svn_client_status_t *
svn_client_status_dup(const svn_client_status_t * status,apr_pool_t * result_pool)714 svn_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 
766 svn_error_t *
svn_client__create_status(svn_client_status_t ** cst,svn_wc_context_t * wc_ctx,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * result_pool,apr_pool_t * scratch_pool)767 svn_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