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_pools.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_delta.h"
38 #include "svn_client.h"
39 #include "svn_error.h"
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_client_private.h"
47 /*** Getting update information ***/
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
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. */
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).
69 This implements the 'svn_wc_status_func4_t' function type. */
71 tweak_status(void *baton,
72 const char *local_abspath,
73 const svn_wc_status3_t *status,
74 apr_pool_t *scratch_pool)
76 struct status_baton *sb = baton;
77 const char *path = local_abspath;
78 svn_client_status_t *cst;
80 if (sb->anchor_abspath)
81 path = svn_dirent_join(sb->anchor_relpath,
82 svn_dirent_skip_ancestor(sb->anchor_abspath, path),
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
88 if (sb->changelist_hash
89 && (! status->changelist
90 || ! svn_hash_gets(sb->changelist_hash, status->changelist)))
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
98 if (sb->deleted_in_repos)
100 svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
101 new_status->repos_node_status = svn_wc_status_deleted;
105 SVN_ERR(svn_client__create_status(&cst, sb->wc_ctx, local_abspath, status,
106 scratch_pool, scratch_pool));
108 /* Call the real status function/baton. */
109 return sb->real_status_func(sb->real_status_baton, path, cst,
113 /* A baton for our reporter that is used to collect locks. */
114 typedef 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. */
119 void *set_locks_baton;
121 svn_client_ctx_t *ctx;
122 /* Pool to store locks in. */
126 /* Implements svn_ra_reporter3_t->set_path. */
128 reporter_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,
133 report_baton_t *rb = report_baton;
135 return rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path,
136 revision, depth, start_empty,
140 /* Implements svn_ra_reporter3_t->delete_path. */
142 reporter_delete_path(void *report_baton, const char *path, apr_pool_t *pool)
144 report_baton_t *rb = report_baton;
146 return rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, path,
150 /* Implements svn_ra_reporter3_t->link_path. */
152 reporter_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)
157 report_baton_t *rb = report_baton;
159 if (!svn_uri__is_ancestor(rb->ancestor, url))
161 const char *ancestor;
163 ancestor = svn_uri_get_longest_ancestor(url, rb->ancestor, pool);
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. */
169 rb->ancestor[strlen(ancestor)] = '\0';
170 rb->depth = svn_depth_infinity;
173 return rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url,
174 revision, depth, start_empty,
178 /* Implements svn_ra_reporter3_t->finish_report. */
180 reporter_finish_report(void *report_baton, apr_pool_t *pool)
182 report_baton_t *rb = report_baton;
183 svn_ra_session_t *ras;
185 const char *repos_root;
186 apr_pool_t *subpool = svn_pool_create(pool);
187 svn_error_t *err = SVN_NO_ERROR;
189 /* Open an RA session to our common ancestor and grab the locks under it.
191 SVN_ERR(svn_client_open_ra_session2(&ras, rb->ancestor, NULL,
192 rb->ctx, subpool, subpool));
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
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)))
201 svn_error_clear(err);
203 locks = apr_hash_make(rb->pool);
207 SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool));
209 /* Close the RA session. */
210 svn_pool_destroy(subpool);
212 SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks,
213 repos_root, rb->pool));
215 return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool);
218 /* Implements svn_ra_reporter3_t->abort_report. */
220 reporter_abort_report(void *report_baton, apr_pool_t *pool)
222 report_baton_t *rb = report_baton;
224 return rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool);
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. */
229 static svn_ra_reporter3_t lock_fetch_reporter = {
231 reporter_delete_path,
233 reporter_finish_report,
234 reporter_abort_report
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().
241 If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide
242 properly formatted relative paths */
244 do_external_status(svn_client_ctx_t *ctx,
245 apr_hash_t *external_map,
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,
254 apr_pool_t *scratch_pool)
256 apr_hash_index_t *hi;
257 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
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
262 for (hi = apr_hash_first(scratch_pool, external_map);
264 hi = apr_hash_next(hi))
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;
273 svn_pool_clear(iterpool);
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));
282 if (external_kind != svn_node_dir)
285 SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool));
286 if (kind != svn_node_dir)
289 if (SVN_IS_VALID_REVNUM(opt_rev.value.number))
290 opt_rev.kind = svn_opt_revision_number;
292 opt_rev.kind = svn_opt_revision_unspecified;
294 /* Tell the client we're starting an external status set. */
295 if (ctx->notify_func2)
298 svn_wc_create_notify(local_abspath,
299 svn_wc_notify_status_external,
300 iterpool), iterpool);
302 status_path = local_abspath;
305 status_path = svn_dirent_join(anchor_relpath,
306 svn_dirent_skip_ancestor(anchor_abspath,
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,
318 /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
319 svn_pool_destroy(iterpool);
324 /*** Public Interface. ***/
328 svn_client_status5(svn_revnum_t *result_rev,
329 svn_client_ctx_t *ctx,
331 const svn_opt_revision_t *revision,
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,
341 apr_pool_t *pool) /* ### aka scratch_pool */
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;
349 apr_hash_t *changelist_hash = NULL;
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);
355 if (changelists && changelists->nelts)
356 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool));
359 *result_rev = SVN_INVALID_REVNUM;
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;
367 SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool));
371 /* The status editor only works on directories, so get the ancestor
374 svn_node_kind_t kind;
376 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
379 /* Dir must be a working copy directory or the status editor fails */
380 if (kind == svn_node_dir)
382 dir_abspath = target_abspath;
383 target_basename = "";
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);
392 if (kind == svn_node_file)
394 if (depth == svn_depth_empty)
395 depth = svn_depth_files;
399 err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath,
402 svn_error_clear(err);
404 if (err || kind != svn_node_dir)
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));
416 dir_abspath = target_abspath;
419 if (svn_dirent_is_absolute(dir))
421 sb.anchor_abspath = NULL;
422 sb.anchor_relpath = NULL;
426 sb.anchor_abspath = dir_abspath;
427 sb.anchor_relpath = dir;
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));
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
439 svn_ra_session_t *ra_session;
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;
447 /* Get full URL from the ANCHOR. */
448 SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx,
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));
457 /* Open a repository session to the URL. */
458 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL,
463 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
464 SVN_RA_CAPABILITY_DEPTH, pool));
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,
470 no_ignore, depth_as_sticky,
471 server_supports_depth,
472 ignores, tweak_status, &sb,
473 ctx->cancel_func, ctx->cancel_baton,
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,
483 if (kind == svn_node_none)
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,
495 sb.deleted_in_repos = TRUE;
497 /* And now close the edit. */
498 SVN_ERR(editor->close_edit(edit_baton, pool));
504 svn_depth_t status_depth;
506 if (revision->kind == svn_opt_revision_head)
508 /* Cause the revision number to be omitted from the request,
509 which implies HEAD. */
510 revnum = SVN_INVALID_REVNUM;
514 /* Get a revision number for our status operation. */
515 SVN_ERR(svn_client__get_revision_number(&revnum, NULL,
518 ra_session, revision,
522 if (depth_as_sticky || !server_supports_depth)
523 status_depth = depth;
525 status_depth = svn_depth_unknown; /* Use depth from WC */
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));
533 /* Init the report baton. */
534 rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */
535 rb.set_locks_baton = set_locks_baton;
539 if (depth == svn_depth_unknown)
540 rb.depth = svn_depth_infinity;
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,
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,
559 if (ctx->notify_func2)
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);
568 /* If the caller wants the result revision, give it to them. */
570 *result_rev = edit_revision;
574 err = svn_wc_walk_status(ctx->wc_ctx, target_abspath,
575 depth, get_all, no_ignore, FALSE, ignores,
577 ctx->cancel_func, ctx->cancel_baton,
580 if (err && err->apr_err == SVN_ERR_WC_MISSING)
582 /* This error code is checked for in svn to continue after
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));
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
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
606 if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
608 apr_hash_t *external_map;
609 SVN_ERR(svn_wc__externals_defined_below(&external_map,
610 ctx->wc_ctx, target_abspath,
614 SVN_ERR(do_external_status(ctx, external_map,
617 sb.anchor_abspath, sb.anchor_relpath,
618 status_func, status_baton, pool));
624 svn_client_status_t *
625 svn_client_status_dup(const svn_client_status_t *status,
626 apr_pool_t *result_pool)
628 svn_client_status_t *st = apr_palloc(result_pool, sizeof(*st));
632 if (status->local_abspath)
633 st->local_abspath = apr_pstrdup(result_pool, status->local_abspath);
635 if (status->repos_root_url)
636 st->repos_root_url = apr_pstrdup(result_pool, status->repos_root_url);
638 if (status->repos_uuid)
639 st->repos_uuid = apr_pstrdup(result_pool, status->repos_uuid);
641 if (status->repos_relpath)
642 st->repos_relpath = apr_pstrdup(result_pool, status->repos_relpath);
644 if (status->changed_author)
645 st->changed_author = apr_pstrdup(result_pool, status->changed_author);
648 st->lock = svn_lock_dup(status->lock, result_pool);
650 if (status->changelist)
651 st->changelist = apr_pstrdup(result_pool, status->changelist);
653 if (status->ood_changed_author)
654 st->ood_changed_author = apr_pstrdup(result_pool, status->ood_changed_author);
656 if (status->repos_lock)
657 st->repos_lock = svn_lock_dup(status->repos_lock, result_pool);
659 if (status->backwards_compatibility_baton)
661 const svn_wc_status3_t *wc_st = status->backwards_compatibility_baton;
663 st->backwards_compatibility_baton = svn_wc_dup_status3(wc_st,
667 if (status->moved_from_abspath)
668 st->moved_from_abspath =
669 apr_pstrdup(result_pool, status->moved_from_abspath);
671 if (status->moved_to_abspath)
672 st->moved_to_abspath = apr_pstrdup(result_pool, status->moved_to_abspath);
678 svn_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)
685 *cst = apr_pcalloc(result_pool, sizeof(**cst));
687 (*cst)->kind = status->kind;
688 (*cst)->local_abspath = local_abspath;
689 (*cst)->filesize = status->filesize;
690 (*cst)->versioned = status->versioned;
692 (*cst)->conflicted = status->conflicted;
694 (*cst)->node_status = status->node_status;
695 (*cst)->text_status = status->text_status;
696 (*cst)->prop_status = status->prop_status;
698 if (status->kind == svn_node_dir)
699 (*cst)->wc_is_locked = status->locked;
701 (*cst)->copied = status->copied;
702 (*cst)->revision = status->revision;
704 (*cst)->changed_rev = status->changed_rev;
705 (*cst)->changed_date = status->changed_date;
706 (*cst)->changed_author = status->changed_author;
708 (*cst)->repos_root_url = status->repos_root_url;
709 (*cst)->repos_uuid = status->repos_uuid;
710 (*cst)->repos_relpath = status->repos_relpath;
712 (*cst)->switched = status->switched;
714 (*cst)->file_external = status->file_external;
715 if (status->file_external)
717 (*cst)->switched = FALSE;
720 (*cst)->lock = status->lock;
722 (*cst)->changelist = status->changelist;
723 (*cst)->depth = status->depth;
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;
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;
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;
740 if (status->versioned && status->conflicted)
742 svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
744 /* Note: This checks the on disk markers to automatically hide
745 text/property conflicts that are hidden by removing their
747 SVN_ERR(svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
748 &tree_conflicted, wc_ctx, local_abspath,
752 (*cst)->text_status = svn_wc_status_conflicted;
755 (*cst)->prop_status = svn_wc_status_conflicted;
757 /* ### Also set this for tree_conflicts? */
758 if (text_conflicted || prop_conflicted)
759 (*cst)->node_status = svn_wc_status_conflicted;
762 (*cst)->moved_from_abspath = status->moved_from_abspath;
763 (*cst)->moved_to_abspath = status->moved_to_abspath;