| /* |
| * status.c: return the status of a working copy dirent |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| /* ==================================================================== */ |
| |
| /* We define this here to remove any further warnings about the usage of |
| experimental functions in this file. */ |
| #define SVN_EXPERIMENTAL |
| |
| |
| /*** Includes. ***/ |
| #include <apr_strings.h> |
| #include <apr_pools.h> |
| |
| #include "svn_private_config.h" |
| #include "svn_pools.h" |
| #include "svn_sorts.h" |
| #include "client.h" |
| |
| #include "svn_path.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_delta.h" |
| #include "svn_client.h" |
| #include "svn_error.h" |
| #include "svn_hash.h" |
| |
| #include "private/svn_client_shelf.h" |
| #include "private/svn_client_private.h" |
| #include "private/svn_sorts_private.h" |
| #include "private/svn_wc_private.h" |
| |
| |
| /*** Getting update information ***/ |
| |
| /* Baton for tweak_status. It wraps a bit of extra functionality |
| around the received status func/baton, so we can remember if the |
| target was deleted in HEAD and tweak incoming status structures |
| accordingly. */ |
| struct status_baton |
| { |
| svn_boolean_t deleted_in_repos; /* target is deleted in repos */ |
| apr_hash_t *changelist_hash; /* keys are changelist names */ |
| svn_client_status_func_t real_status_func; /* real status function */ |
| void *real_status_baton; /* real status baton */ |
| const char *anchor_abspath; /* Absolute path of anchor */ |
| const char *anchor_relpath; /* Relative path of anchor */ |
| svn_wc_context_t *wc_ctx; /* A working copy context. */ |
| }; |
| |
| /* A status callback function which wraps the *real* status |
| function/baton. This sucker takes care of any status tweaks we |
| need to make (such as noting that the target of the status is |
| missing from HEAD in the repository). |
| |
| This implements the 'svn_wc_status_func4_t' function type. */ |
| static svn_error_t * |
| tweak_status(void *baton, |
| const char *local_abspath, |
| const svn_wc_status3_t *status, |
| apr_pool_t *scratch_pool) |
| { |
| struct status_baton *sb = baton; |
| const char *path = local_abspath; |
| svn_client_status_t *cst; |
| |
| if (sb->anchor_abspath) |
| path = svn_dirent_join(sb->anchor_relpath, |
| svn_dirent_skip_ancestor(sb->anchor_abspath, path), |
| scratch_pool); |
| |
| /* If the status item has an entry, but doesn't belong to one of the |
| changelists our caller is interested in, we filter out this status |
| transmission. */ |
| if (sb->changelist_hash |
| && (! status->changelist |
| || ! svn_hash_gets(sb->changelist_hash, status->changelist))) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| /* If we know that the target was deleted in HEAD of the repository, |
| we need to note that fact in all the status structures that come |
| through here. */ |
| if (sb->deleted_in_repos) |
| { |
| svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool); |
| new_status->repos_node_status = svn_wc_status_deleted; |
| status = new_status; |
| } |
| |
| SVN_ERR(svn_client__create_status(&cst, sb->wc_ctx, local_abspath, status, |
| scratch_pool, scratch_pool)); |
| |
| /* Call the real status function/baton. */ |
| return sb->real_status_func(sb->real_status_baton, path, cst, |
| scratch_pool); |
| } |
| |
| /* A baton for our reporter that is used to collect locks. */ |
| typedef struct report_baton_t { |
| const svn_ra_reporter3_t* wrapped_reporter; |
| void *wrapped_report_baton; |
| /* The common ancestor URL of all paths included in the report. */ |
| char *ancestor; |
| void *set_locks_baton; |
| svn_depth_t depth; |
| svn_client_ctx_t *ctx; |
| /* Pool to store locks in. */ |
| apr_pool_t *pool; |
| } report_baton_t; |
| |
| /* Implements svn_ra_reporter3_t->set_path. */ |
| 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) |
| { |
| report_baton_t *rb = report_baton; |
| |
| return rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path, |
| revision, depth, start_empty, |
| lock_token, pool); |
| } |
| |
| /* Implements svn_ra_reporter3_t->delete_path. */ |
| static svn_error_t * |
| reporter_delete_path(void *report_baton, const char *path, apr_pool_t *pool) |
| { |
| report_baton_t *rb = report_baton; |
| |
| return rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, path, |
| pool); |
| } |
| |
| /* Implements svn_ra_reporter3_t->link_path. */ |
| 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) |
| { |
| report_baton_t *rb = report_baton; |
| |
| if (!svn_uri__is_ancestor(rb->ancestor, url)) |
| { |
| const char *ancestor; |
| |
| ancestor = svn_uri_get_longest_ancestor(url, rb->ancestor, pool); |
| |
| /* If we got a shorter ancestor, truncate our current ancestor. |
| Note that svn_uri_get_longest_ancestor will allocate its return |
| value even if it identical to one of its arguments. */ |
| |
| rb->ancestor[strlen(ancestor)] = '\0'; |
| rb->depth = svn_depth_infinity; |
| } |
| |
| return rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url, |
| revision, depth, start_empty, |
| lock_token, pool); |
| } |
| |
| /* Implements svn_ra_reporter3_t->finish_report. */ |
| static svn_error_t * |
| reporter_finish_report(void *report_baton, apr_pool_t *pool) |
| { |
| report_baton_t *rb = report_baton; |
| svn_ra_session_t *ras; |
| apr_hash_t *locks; |
| const char *repos_root; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| svn_error_t *err = SVN_NO_ERROR; |
| |
| /* Open an RA session to our common ancestor and grab the locks under it. |
| */ |
| SVN_ERR(svn_client_open_ra_session2(&ras, rb->ancestor, NULL, |
| rb->ctx, subpool, subpool)); |
| |
| /* The locks need to live throughout the edit. Note that if the |
| server doesn't support lock discovery, we'll just not do locky |
| stuff. */ |
| err = svn_ra_get_locks2(ras, &locks, "", rb->depth, rb->pool); |
| if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) |
| { |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| locks = apr_hash_make(rb->pool); |
| } |
| SVN_ERR(err); |
| |
| SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool)); |
| |
| /* Close the RA session. */ |
| svn_pool_destroy(subpool); |
| |
| SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks, |
| repos_root, rb->pool)); |
| |
| return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool); |
| } |
| |
| /* Implements svn_ra_reporter3_t->abort_report. */ |
| static svn_error_t * |
| reporter_abort_report(void *report_baton, apr_pool_t *pool) |
| { |
| report_baton_t *rb = report_baton; |
| |
| return rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool); |
| } |
| |
| /* A reporter that keeps track of the common URL ancestor of all paths in |
| the WC and fetches repository locks for all paths under this ancestor. */ |
| static svn_ra_reporter3_t lock_fetch_reporter = { |
| reporter_set_path, |
| reporter_delete_path, |
| reporter_link_path, |
| reporter_finish_report, |
| reporter_abort_report |
| }; |
| |
| /* Perform status operations on each external in EXTERNAL_MAP, a const char * |
| local_abspath of all externals mapping to the const char* defining_abspath. |
| All other options are the same as those passed to svn_client_status(). |
| |
| If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide |
| properly formatted relative paths */ |
| 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) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| apr_array_header_t *externals; |
| int i; |
| |
| externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically, |
| scratch_pool); |
| |
| /* Loop over the hash of new values (we don't care about the old |
| ones). This is a mapping of versioned directories to property |
| values. */ |
| for (i = 0; i < externals->nelts; i++) |
| { |
| svn_node_kind_t external_kind; |
| svn_sort__item_t item = APR_ARRAY_IDX(externals, i, svn_sort__item_t); |
| const char *local_abspath = item.key; |
| const char *defining_abspath = item.value; |
| svn_node_kind_t kind; |
| svn_opt_revision_t opt_rev; |
| const char *status_path; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Obtain information on the expected external. */ |
| SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, |
| &opt_rev.value.number, |
| ctx->wc_ctx, defining_abspath, |
| local_abspath, FALSE, |
| iterpool, iterpool)); |
| |
| if (external_kind != svn_node_dir) |
| continue; |
| |
| SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool)); |
| if (kind != svn_node_dir) |
| continue; |
| |
| if (SVN_IS_VALID_REVNUM(opt_rev.value.number)) |
| opt_rev.kind = svn_opt_revision_number; |
| else |
| opt_rev.kind = svn_opt_revision_unspecified; |
| |
| /* Tell the client we're starting an external status set. */ |
| if (ctx->notify_func2) |
| ctx->notify_func2( |
| ctx->notify_baton2, |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_status_external, |
| iterpool), iterpool); |
| |
| status_path = local_abspath; |
| if (anchor_abspath) |
| { |
| status_path = svn_dirent_join(anchor_relpath, |
| svn_dirent_skip_ancestor(anchor_abspath, |
| status_path), |
| iterpool); |
| } |
| |
| /* And then do the status. */ |
| SVN_ERR(svn_client_status6(NULL, ctx, status_path, &opt_rev, depth, |
| get_all, check_out_of_date, |
| check_working_copy, no_ignore, |
| FALSE /* ignore_exernals */, |
| FALSE /* depth_as_sticky */, |
| changelists, status_func, status_baton, |
| iterpool)); |
| } |
| |
| /* Destroy SUBPOOL and (implicitly) ITERPOOL. */ |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Run status on shelf SHELF_NAME, if it exists. |
| */ |
| 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) |
| { |
| svn_error_t *err; |
| svn_client__shelf_t *shelf; |
| svn_client__shelf_version_t *shelf_version; |
| const char *wc_relpath; |
| |
| err = svn_client__shelf_open_existing(&shelf, |
| shelf_name, target_abspath, |
| ctx, scratch_pool); |
| if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) |
| { |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| else |
| SVN_ERR(err); |
| |
| SVN_ERR(svn_client__shelf_version_open(&shelf_version, |
| shelf, shelf->max_version, |
| scratch_pool, scratch_pool)); |
| wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); |
| SVN_ERR(svn_client__shelf_version_status_walk(shelf_version, wc_relpath, |
| status_func, status_baton, |
| scratch_pool)); |
| SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Run status on all shelves named in CHANGELISTS by a changelist name |
| * of the form "svn:shelf:SHELF_NAME", if they exist. |
| */ |
| 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) |
| { |
| static const char PREFIX[] = "svn:shelf:"; |
| static const int PREFIX_LEN = 10; |
| int i; |
| |
| if (! changelists) |
| return SVN_NO_ERROR; |
| for (i = 0; i < changelists->nelts; i++) |
| { |
| const char *cl = APR_ARRAY_IDX(changelists, i, const char *); |
| |
| if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) |
| { |
| const char *shelf_name = cl + PREFIX_LEN; |
| |
| SVN_ERR(shelf_status(shelf_name, target_abspath, |
| status_func, status_baton, |
| ctx, scratch_pool)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /*** Public Interface. ***/ |
| |
| |
| 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) /* ### aka scratch_pool */ |
| { |
| struct status_baton sb; |
| const char *dir, *dir_abspath; |
| const char *target_abspath; |
| const char *target_basename; |
| apr_array_header_t *ignores; |
| svn_error_t *err; |
| apr_hash_t *changelist_hash = NULL; |
| |
| /* Override invalid combinations of the check_out_of_date and |
| check_working_copy flags. */ |
| if (!check_out_of_date) |
| check_working_copy = TRUE; |
| |
| if (svn_path_is_url(path)) |
| return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, |
| _("'%s' is not a local path"), path); |
| |
| if (changelists && changelists->nelts) |
| SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); |
| |
| if (result_rev) |
| *result_rev = SVN_INVALID_REVNUM; |
| |
| sb.real_status_func = status_func; |
| sb.real_status_baton = status_baton; |
| sb.deleted_in_repos = FALSE; |
| sb.changelist_hash = changelist_hash; |
| sb.wc_ctx = ctx->wc_ctx; |
| |
| SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool)); |
| |
| if (check_out_of_date) |
| { |
| /* The status editor only works on directories, so get the ancestor |
| if necessary */ |
| |
| svn_node_kind_t kind; |
| |
| SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, |
| TRUE, FALSE, pool)); |
| |
| /* Dir must be a working copy directory or the status editor fails */ |
| if (kind == svn_node_dir) |
| { |
| dir_abspath = target_abspath; |
| target_basename = ""; |
| dir = path; |
| } |
| else |
| { |
| dir_abspath = svn_dirent_dirname(target_abspath, pool); |
| target_basename = svn_dirent_basename(target_abspath, NULL); |
| dir = svn_dirent_dirname(path, pool); |
| |
| if (kind == svn_node_file) |
| { |
| if (depth == svn_depth_empty) |
| depth = svn_depth_files; |
| } |
| else |
| { |
| err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath, |
| FALSE, FALSE, pool); |
| |
| svn_error_clear(err); |
| |
| if (err || kind != svn_node_dir) |
| { |
| return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, |
| _("'%s' is not a working copy"), |
| svn_dirent_local_style(path, pool)); |
| } |
| } |
| } |
| } |
| else |
| { |
| dir = path; |
| dir_abspath = target_abspath; |
| } |
| |
| if (svn_dirent_is_absolute(dir)) |
| { |
| sb.anchor_abspath = NULL; |
| sb.anchor_relpath = NULL; |
| } |
| else |
| { |
| sb.anchor_abspath = dir_abspath; |
| sb.anchor_relpath = dir; |
| } |
| |
| /* Get the status edit, and use our wrapping status function/baton |
| as the callback pair. */ |
| SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); |
| |
| /* If we want to know about out-of-dateness, we crawl the working copy and |
| let the RA layer drive the editor for real. Otherwise, we just close the |
| edit. :-) */ |
| if (check_out_of_date) |
| { |
| svn_ra_session_t *ra_session; |
| const char *URL; |
| svn_node_kind_t kind; |
| svn_boolean_t server_supports_depth; |
| const svn_delta_editor_t *editor; |
| void *edit_baton, *set_locks_baton; |
| svn_revnum_t edit_revision = SVN_INVALID_REVNUM; |
| |
| /* Get full URL from the ANCHOR. */ |
| SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx, |
| pool, pool)); |
| |
| if (!URL) |
| return svn_error_createf |
| (SVN_ERR_ENTRY_MISSING_URL, NULL, |
| _("Entry '%s' has no URL"), |
| svn_dirent_local_style(dir, pool)); |
| |
| /* Open a repository session to the URL. */ |
| SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, |
| dir_abspath, NULL, |
| FALSE, TRUE, |
| ctx, pool, pool)); |
| |
| SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, |
| SVN_RA_CAPABILITY_DEPTH, pool)); |
| |
| SVN_ERR(svn_wc__get_status_editor(&editor, &edit_baton, &set_locks_baton, |
| &edit_revision, ctx->wc_ctx, |
| dir_abspath, target_basename, |
| depth, get_all, check_working_copy, |
| no_ignore, depth_as_sticky, |
| server_supports_depth, |
| ignores, tweak_status, &sb, |
| ctx->cancel_func, ctx->cancel_baton, |
| pool, pool)); |
| |
| |
| /* Verify that URL exists in HEAD. If it doesn't, this can save |
| us a whole lot of hassle; if it does, the cost of this |
| request should be minimal compared to the size of getting |
| back the average amount of "out-of-date" information. */ |
| SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, |
| &kind, pool)); |
| if (kind == svn_node_none) |
| { |
| svn_boolean_t added; |
| |
| /* Our status target does not exist in HEAD. If we've got |
| it locally added, that's okay. But if it was previously |
| versioned, then it must have since been deleted from the |
| repository. (Note that "locally replaced" doesn't count |
| as "added" in this case.) */ |
| SVN_ERR(svn_wc__node_is_added(&added, ctx->wc_ctx, |
| dir_abspath, pool)); |
| if (! added) |
| sb.deleted_in_repos = TRUE; |
| |
| /* And now close the edit. */ |
| SVN_ERR(editor->close_edit(edit_baton, pool)); |
| } |
| else |
| { |
| svn_revnum_t revnum; |
| report_baton_t rb; |
| svn_depth_t status_depth; |
| |
| if (revision->kind == svn_opt_revision_head) |
| { |
| /* Cause the revision number to be omitted from the request, |
| which implies HEAD. */ |
| revnum = SVN_INVALID_REVNUM; |
| } |
| else |
| { |
| /* Get a revision number for our status operation. */ |
| SVN_ERR(svn_client__get_revision_number(&revnum, NULL, |
| ctx->wc_ctx, |
| target_abspath, |
| ra_session, revision, |
| pool)); |
| } |
| |
| if (depth_as_sticky || !server_supports_depth) |
| status_depth = depth; |
| else |
| status_depth = svn_depth_unknown; /* Use depth from WC */ |
| |
| /* Do the deed. Let the RA layer drive the status editor. */ |
| SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter, |
| &rb.wrapped_report_baton, |
| target_basename, revnum, status_depth, |
| editor, edit_baton, pool)); |
| |
| /* Init the report baton. */ |
| rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */ |
| rb.set_locks_baton = set_locks_baton; |
| rb.ctx = ctx; |
| rb.pool = pool; |
| |
| if (depth == svn_depth_unknown) |
| rb.depth = svn_depth_infinity; |
| else |
| rb.depth = depth; |
| |
| /* Drive the reporter structure, describing the revisions |
| within PATH. When we call reporter->finish_report, |
| EDITOR will be driven to describe differences between our |
| working copy and HEAD. */ |
| SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, |
| target_abspath, |
| &lock_fetch_reporter, &rb, |
| FALSE /* restore_files */, |
| depth, (! depth_as_sticky), |
| (! server_supports_depth), |
| FALSE /* use_commit_times */, |
| ctx->cancel_func, ctx->cancel_baton, |
| NULL, NULL, pool)); |
| } |
| |
| if (ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify |
| = svn_wc_create_notify(target_abspath, |
| svn_wc_notify_status_completed, pool); |
| notify->revision = edit_revision; |
| ctx->notify_func2(ctx->notify_baton2, notify, pool); |
| } |
| |
| /* If the caller wants the result revision, give it to them. */ |
| if (result_rev) |
| *result_rev = edit_revision; |
| } |
| else |
| { |
| SVN_ERR(shelves_status(changelists, target_abspath, |
| tweak_status, &sb, |
| ctx, pool)); |
| err = svn_wc_walk_status(ctx->wc_ctx, target_abspath, |
| depth, get_all, no_ignore, FALSE, ignores, |
| tweak_status, &sb, |
| ctx->cancel_func, ctx->cancel_baton, |
| pool); |
| |
| if (err && err->apr_err == SVN_ERR_WC_MISSING) |
| { |
| /* This error code is checked for in svn to continue after |
| this error */ |
| svn_error_clear(err); |
| return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, |
| _("'%s' is not a working copy"), |
| svn_dirent_local_style(path, pool)); |
| } |
| |
| SVN_ERR(err); |
| } |
| |
| /* We only descend into an external if depth is svn_depth_infinity or |
| svn_depth_unknown. However, there are conceivable behaviors that |
| would involve descending under other circumstances; thus, we pass |
| depth anyway, so the code will DTRT if we change the conditional |
| in the future. |
| */ |
| if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) |
| { |
| apr_hash_t *external_map; |
| SVN_ERR(svn_wc__externals_defined_below(&external_map, |
| ctx->wc_ctx, target_abspath, |
| pool, pool)); |
| |
| |
| SVN_ERR(do_external_status(ctx, external_map, |
| depth, get_all, |
| check_out_of_date, check_working_copy, |
| no_ignore, changelists, |
| sb.anchor_abspath, sb.anchor_relpath, |
| status_func, status_baton, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_client_status_t * |
| svn_client_status_dup(const svn_client_status_t *status, |
| apr_pool_t *result_pool) |
| { |
| svn_client_status_t *st = apr_palloc(result_pool, sizeof(*st)); |
| |
| *st = *status; |
| |
| if (status->local_abspath) |
| st->local_abspath = apr_pstrdup(result_pool, status->local_abspath); |
| |
| if (status->repos_root_url) |
| st->repos_root_url = apr_pstrdup(result_pool, status->repos_root_url); |
| |
| if (status->repos_uuid) |
| st->repos_uuid = apr_pstrdup(result_pool, status->repos_uuid); |
| |
| if (status->repos_relpath) |
| st->repos_relpath = apr_pstrdup(result_pool, status->repos_relpath); |
| |
| if (status->changed_author) |
| st->changed_author = apr_pstrdup(result_pool, status->changed_author); |
| |
| if (status->lock) |
| st->lock = svn_lock_dup(status->lock, result_pool); |
| |
| if (status->changelist) |
| st->changelist = apr_pstrdup(result_pool, status->changelist); |
| |
| if (status->ood_changed_author) |
| st->ood_changed_author = apr_pstrdup(result_pool, status->ood_changed_author); |
| |
| if (status->repos_lock) |
| st->repos_lock = svn_lock_dup(status->repos_lock, result_pool); |
| |
| if (status->backwards_compatibility_baton) |
| { |
| const svn_wc_status3_t *wc_st = status->backwards_compatibility_baton; |
| |
| st->backwards_compatibility_baton = svn_wc_dup_status3(wc_st, |
| result_pool); |
| } |
| |
| if (status->moved_from_abspath) |
| st->moved_from_abspath = |
| apr_pstrdup(result_pool, status->moved_from_abspath); |
| |
| if (status->moved_to_abspath) |
| st->moved_to_abspath = apr_pstrdup(result_pool, status->moved_to_abspath); |
| |
| return st; |
| } |
| |
| 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) |
| { |
| *cst = apr_pcalloc(result_pool, sizeof(**cst)); |
| |
| (*cst)->kind = status->kind; |
| (*cst)->local_abspath = local_abspath; |
| (*cst)->filesize = status->filesize; |
| (*cst)->versioned = status->versioned; |
| |
| (*cst)->conflicted = status->conflicted; |
| |
| (*cst)->node_status = status->node_status; |
| (*cst)->text_status = status->text_status; |
| (*cst)->prop_status = status->prop_status; |
| |
| if (status->kind == svn_node_dir) |
| (*cst)->wc_is_locked = status->locked; |
| |
| (*cst)->copied = status->copied; |
| (*cst)->revision = status->revision; |
| |
| (*cst)->changed_rev = status->changed_rev; |
| (*cst)->changed_date = status->changed_date; |
| (*cst)->changed_author = status->changed_author; |
| |
| (*cst)->repos_root_url = status->repos_root_url; |
| (*cst)->repos_uuid = status->repos_uuid; |
| (*cst)->repos_relpath = status->repos_relpath; |
| |
| (*cst)->switched = status->switched; |
| |
| (*cst)->file_external = status->file_external; |
| if (status->file_external) |
| { |
| (*cst)->switched = FALSE; |
| } |
| |
| (*cst)->lock = status->lock; |
| |
| (*cst)->changelist = status->changelist; |
| (*cst)->depth = status->depth; |
| |
| /* Out of date information */ |
| (*cst)->ood_kind = status->ood_kind; |
| (*cst)->repos_node_status = status->repos_node_status; |
| (*cst)->repos_text_status = status->repos_text_status; |
| (*cst)->repos_prop_status = status->repos_prop_status; |
| (*cst)->repos_lock = status->repos_lock; |
| |
| (*cst)->ood_changed_rev = status->ood_changed_rev; |
| (*cst)->ood_changed_date = status->ood_changed_date; |
| (*cst)->ood_changed_author = status->ood_changed_author; |
| |
| /* When changing the value of backwards_compatibility_baton, also |
| change its use in status4_wrapper_func in deprecated.c */ |
| (*cst)->backwards_compatibility_baton = status; |
| |
| if (status->versioned && status->conflicted) |
| { |
| svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; |
| |
| /* Note: This checks the on disk markers to automatically hide |
| text/property conflicts that are hidden by removing their |
| markers */ |
| SVN_ERR(svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, |
| &tree_conflicted, wc_ctx, local_abspath, |
| scratch_pool)); |
| |
| if (text_conflicted) |
| (*cst)->text_status = svn_wc_status_conflicted; |
| |
| if (prop_conflicted) |
| (*cst)->prop_status = svn_wc_status_conflicted; |
| |
| /* ### Also set this for tree_conflicts? */ |
| if (text_conflicted || prop_conflicted) |
| (*cst)->node_status = svn_wc_status_conflicted; |
| } |
| |
| (*cst)->moved_from_abspath = status->moved_from_abspath; |
| (*cst)->moved_to_abspath = status->moved_to_abspath; |
| |
| return SVN_NO_ERROR; |
| } |
| |