2 * status.c: return the status of a working copy dirent
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
29 #include <apr_strings.h>
30 #include <apr_pools.h>
32 #include "svn_private_config.h"
33 #include "svn_pools.h"
34 #include "svn_sorts.h"
38 #include "svn_dirent_uri.h"
39 #include "svn_delta.h"
40 #include "svn_client.h"
41 #include "svn_error.h"
44 #include "private/svn_client_private.h"
45 #include "private/svn_sorts_private.h"
46 #include "private/svn_wc_private.h"
49 /*** Getting update information ***/
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
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. */
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).
71 This implements the 'svn_wc_status_func4_t' function type. */
73 tweak_status(void *baton,
74 const char *local_abspath,
75 const svn_wc_status3_t *status,
76 apr_pool_t *scratch_pool)
78 struct status_baton *sb = baton;
79 const char *path = local_abspath;
80 svn_client_status_t *cst;
82 if (sb->anchor_abspath)
83 path = svn_dirent_join(sb->anchor_relpath,
84 svn_dirent_skip_ancestor(sb->anchor_abspath, path),
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
90 if (sb->changelist_hash
91 && (! status->changelist
92 || ! svn_hash_gets(sb->changelist_hash, status->changelist)))
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
100 if (sb->deleted_in_repos)
102 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
103 new_status->repos_node_status = svn_wc_status_deleted;
107 SVN_ERR(svn_client__create_status(&cst, sb->wc_ctx, local_abspath, status,
108 scratch_pool, scratch_pool));
110 /* Call the real status function/baton. */
111 return sb->real_status_func(sb->real_status_baton, path, cst,
115 /* A baton for our reporter that is used to collect locks. */
116 typedef 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. */
121 void *set_locks_baton;
123 svn_client_ctx_t *ctx;
124 /* Pool to store locks in. */
128 /* Implements svn_ra_reporter3_t->set_path. */
130 reporter_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,
135 report_baton_t *rb = report_baton;
137 return rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path,
138 revision, depth, start_empty,
142 /* Implements svn_ra_reporter3_t->delete_path. */
144 reporter_delete_path(void *report_baton, const char *path, apr_pool_t *pool)
146 report_baton_t *rb = report_baton;
148 return rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, path,
152 /* Implements svn_ra_reporter3_t->link_path. */
154 reporter_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)
159 report_baton_t *rb = report_baton;
161 if (!svn_uri__is_ancestor(rb->ancestor, url))
163 const char *ancestor;
165 ancestor = svn_uri_get_longest_ancestor(url, rb->ancestor, pool);
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. */
171 rb->ancestor[strlen(ancestor)] = '\0';
172 rb->depth = svn_depth_infinity;
175 return rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url,
176 revision, depth, start_empty,
180 /* Implements svn_ra_reporter3_t->finish_report. */
182 reporter_finish_report(void *report_baton, apr_pool_t *pool)
184 report_baton_t *rb = report_baton;
185 svn_ra_session_t *ras;
187 const char *repos_root;
188 apr_pool_t *subpool = svn_pool_create(pool);
189 svn_error_t *err = SVN_NO_ERROR;
191 /* Open an RA session to our common ancestor and grab the locks under it.
193 SVN_ERR(svn_client_open_ra_session2(&ras, rb->ancestor, NULL,
194 rb->ctx, subpool, subpool));
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
199 err = svn_ra_get_locks2(ras, &locks, "", rb->depth, rb->pool);
200 if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
202 svn_error_clear(err);
204 locks = apr_hash_make(rb->pool);
208 SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool));
210 /* Close the RA session. */
211 svn_pool_destroy(subpool);
213 SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks,
214 repos_root, rb->pool));
216 return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool);
219 /* Implements svn_ra_reporter3_t->abort_report. */
221 reporter_abort_report(void *report_baton, apr_pool_t *pool)
223 report_baton_t *rb = report_baton;
225 return rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool);
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. */
230 static svn_ra_reporter3_t lock_fetch_reporter = {
232 reporter_delete_path,
234 reporter_finish_report,
235 reporter_abort_report
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().
242 If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide
243 properly formatted relative paths */
245 do_external_status(svn_client_ctx_t *ctx,
246 apr_hash_t *external_map,
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,
257 apr_pool_t *scratch_pool)
259 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
260 apr_array_header_t *externals;
263 externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically,
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
269 for (i = 0; i < externals->nelts; i++)
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;
279 svn_pool_clear(iterpool);
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));
288 if (external_kind != svn_node_dir)
291 SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool));
292 if (kind != svn_node_dir)
295 if (SVN_IS_VALID_REVNUM(opt_rev.value.number))
296 opt_rev.kind = svn_opt_revision_number;
298 opt_rev.kind = svn_opt_revision_unspecified;
300 /* Tell the client we're starting an external status set. */
301 if (ctx->notify_func2)
304 svn_wc_create_notify(local_abspath,
305 svn_wc_notify_status_external,
306 iterpool), iterpool);
308 status_path = local_abspath;
311 status_path = svn_dirent_join(anchor_relpath,
312 svn_dirent_skip_ancestor(anchor_abspath,
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,
327 /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
328 svn_pool_destroy(iterpool);
333 /*** Public Interface. ***/
337 svn_client_status6(svn_revnum_t *result_rev,
338 svn_client_ctx_t *ctx,
340 const svn_opt_revision_t *revision,
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,
351 apr_pool_t *pool) /* ### aka scratch_pool */
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;
359 apr_hash_t *changelist_hash = NULL;
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;
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);
370 if (changelists && changelists->nelts)
371 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool));
374 *result_rev = SVN_INVALID_REVNUM;
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;
382 SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool));
384 if (check_out_of_date)
386 /* The status editor only works on directories, so get the ancestor
389 svn_node_kind_t kind;
391 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
394 /* Dir must be a working copy directory or the status editor fails */
395 if (kind == svn_node_dir)
397 dir_abspath = target_abspath;
398 target_basename = "";
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);
407 if (kind == svn_node_file)
409 if (depth == svn_depth_empty)
410 depth = svn_depth_files;
414 err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath,
417 svn_error_clear(err);
419 if (err || kind != svn_node_dir)
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));
431 dir_abspath = target_abspath;
434 if (svn_dirent_is_absolute(dir))
436 sb.anchor_abspath = NULL;
437 sb.anchor_relpath = NULL;
441 sb.anchor_abspath = dir_abspath;
442 sb.anchor_relpath = dir;
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));
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
452 if (check_out_of_date)
454 svn_ra_session_t *ra_session;
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;
462 /* Get full URL from the ANCHOR. */
463 SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx,
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));
472 /* Open a repository session to the URL. */
473 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL,
478 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
479 SVN_RA_CAPABILITY_DEPTH, pool));
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,
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,
498 if (kind == svn_node_none)
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,
510 sb.deleted_in_repos = TRUE;
512 /* And now close the edit. */
513 SVN_ERR(editor->close_edit(edit_baton, pool));
519 svn_depth_t status_depth;
521 if (revision->kind == svn_opt_revision_head)
523 /* Cause the revision number to be omitted from the request,
524 which implies HEAD. */
525 revnum = SVN_INVALID_REVNUM;
529 /* Get a revision number for our status operation. */
530 SVN_ERR(svn_client__get_revision_number(&revnum, NULL,
533 ra_session, revision,
537 if (depth_as_sticky || !server_supports_depth)
538 status_depth = depth;
540 status_depth = svn_depth_unknown; /* Use depth from WC */
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));
548 /* Init the report baton. */
549 rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */
550 rb.set_locks_baton = set_locks_baton;
554 if (depth == svn_depth_unknown)
555 rb.depth = svn_depth_infinity;
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,
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,
574 if (ctx->notify_func2)
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);
583 /* If the caller wants the result revision, give it to them. */
585 *result_rev = edit_revision;
589 err = svn_wc_walk_status(ctx->wc_ctx, target_abspath,
590 depth, get_all, no_ignore, FALSE, ignores,
592 ctx->cancel_func, ctx->cancel_baton,
595 if (err && err->apr_err == SVN_ERR_WC_MISSING)
597 /* This error code is checked for in svn to continue after
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));
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
614 if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
616 apr_hash_t *external_map;
617 SVN_ERR(svn_wc__externals_defined_below(&external_map,
618 ctx->wc_ctx, target_abspath,
622 SVN_ERR(do_external_status(ctx, external_map,
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));
633 svn_client_status_t *
634 svn_client_status_dup(const svn_client_status_t *status,
635 apr_pool_t *result_pool)
637 svn_client_status_t *st = apr_palloc(result_pool, sizeof(*st));
641 if (status->local_abspath)
642 st->local_abspath = apr_pstrdup(result_pool, status->local_abspath);
644 if (status->repos_root_url)
645 st->repos_root_url = apr_pstrdup(result_pool, status->repos_root_url);
647 if (status->repos_uuid)
648 st->repos_uuid = apr_pstrdup(result_pool, status->repos_uuid);
650 if (status->repos_relpath)
651 st->repos_relpath = apr_pstrdup(result_pool, status->repos_relpath);
653 if (status->changed_author)
654 st->changed_author = apr_pstrdup(result_pool, status->changed_author);
657 st->lock = svn_lock_dup(status->lock, result_pool);
659 if (status->changelist)
660 st->changelist = apr_pstrdup(result_pool, status->changelist);
662 if (status->ood_changed_author)
663 st->ood_changed_author = apr_pstrdup(result_pool, status->ood_changed_author);
665 if (status->repos_lock)
666 st->repos_lock = svn_lock_dup(status->repos_lock, result_pool);
668 if (status->backwards_compatibility_baton)
670 const svn_wc_status3_t *wc_st = status->backwards_compatibility_baton;
672 st->backwards_compatibility_baton = svn_wc_dup_status3(wc_st,
676 if (status->moved_from_abspath)
677 st->moved_from_abspath =
678 apr_pstrdup(result_pool, status->moved_from_abspath);
680 if (status->moved_to_abspath)
681 st->moved_to_abspath = apr_pstrdup(result_pool, status->moved_to_abspath);
687 svn_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)
694 *cst = apr_pcalloc(result_pool, sizeof(**cst));
696 (*cst)->kind = status->kind;
697 (*cst)->local_abspath = local_abspath;
698 (*cst)->filesize = status->filesize;
699 (*cst)->versioned = status->versioned;
701 (*cst)->conflicted = status->conflicted;
703 (*cst)->node_status = status->node_status;
704 (*cst)->text_status = status->text_status;
705 (*cst)->prop_status = status->prop_status;
707 if (status->kind == svn_node_dir)
708 (*cst)->wc_is_locked = status->locked;
710 (*cst)->copied = status->copied;
711 (*cst)->revision = status->revision;
713 (*cst)->changed_rev = status->changed_rev;
714 (*cst)->changed_date = status->changed_date;
715 (*cst)->changed_author = status->changed_author;
717 (*cst)->repos_root_url = status->repos_root_url;
718 (*cst)->repos_uuid = status->repos_uuid;
719 (*cst)->repos_relpath = status->repos_relpath;
721 (*cst)->switched = status->switched;
723 (*cst)->file_external = status->file_external;
724 if (status->file_external)
726 (*cst)->switched = FALSE;
729 (*cst)->lock = status->lock;
731 (*cst)->changelist = status->changelist;
732 (*cst)->depth = status->depth;
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;
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;
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;
749 if (status->versioned && status->conflicted)
751 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
753 /* Note: This checks the on disk markers to automatically hide
754 text/property conflicts that are hidden by removing their
756 SVN_ERR(svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
757 &tree_conflicted, wc_ctx, local_abspath,
761 (*cst)->text_status = svn_wc_status_conflicted;
764 (*cst)->prop_status = svn_wc_status_conflicted;
766 /* ### Also set this for tree_conflicts? */
767 if (text_conflicted || prop_conflicted)
768 (*cst)->node_status = svn_wc_status_conflicted;
771 (*cst)->moved_from_abspath = status->moved_from_abspath;
772 (*cst)->moved_to_abspath = status->moved_to_abspath;