/* Determine if LOCAL_ABSPATH needs an inherited property cache. If it does, then set *NEEDS_CACHE to TRUE, set it to FALSE otherwise. All other args are as per svn_client__get_inheritable_props(). */ static svn_error_t * need_to_cache_iprops(svn_boolean_t *needs_cache, const char *local_abspath, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_boolean_t is_wc_root; svn_boolean_t is_switched; svn_error_t *err; err = svn_wc_check_root(&is_wc_root, &is_switched, NULL, ctx->wc_ctx, local_abspath, scratch_pool); /* LOCAL_ABSPATH doesn't need a cache if it doesn't exist. */ if (err) { if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); is_wc_root = FALSE; is_switched = FALSE; } else { return svn_error_trace(err); } } /* Starting assumption. */ *needs_cache = FALSE; if (is_wc_root || is_switched) { const char *session_url; const char *session_root_url; /* Looks likely that we need an inherited properties cache...Unless LOCAL_ABSPATH is a WC root that points to the repos root. Then it doesn't need a cache because it has nowhere to inherit from. Check for that case. */ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_root_url, scratch_pool)); if (strcmp(session_root_url, session_url) != 0) *needs_cache = TRUE; } return SVN_NO_ERROR; }
/* Set *FS_PATH_P to the absolute filesystem path associated with the URL built from SESSION's URL and REL_PATH (which is relative to session's URL. Use POOL for allocations. */ static svn_error_t * get_fs_path(const char **fs_path_p, svn_ra_session_t *session, const char *rel_path, apr_pool_t *pool) { const char *url, *fs_path; SVN_ERR(svn_ra_get_session_url(session, &url, pool)); SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool)); *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path, rel_path, pool), pool); return SVN_NO_ERROR; }
svn_error_t * svn_client__get_inheritable_props(apr_hash_t **wcroot_iprops, const char *local_abspath, svn_revnum_t revision, svn_depth_t depth, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *old_session_url; svn_error_t *err; if (!SVN_IS_VALID_REVNUM(revision)) return SVN_NO_ERROR; if (ra_session) SVN_ERR(svn_ra_get_session_url(ra_session, &old_session_url, scratch_pool)); /* We just wrap a simple helper function, as it is to easy to leave the ra session rooted at some wrong path without a wrapper like this. During development we had problems where some now deleted switched path made the update try to update to that url instead of the intended url */ err = get_inheritable_props(wcroot_iprops, local_abspath, revision, depth, ra_session, ctx, result_pool, scratch_pool); if (ra_session) { err = svn_error_compose_create( err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)); } return svn_error_trace(err); }
static svn_error_t * switch_internal(svn_revnum_t *result_rev, const char *local_abspath, const char *anchor_abspath, const char *switch_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_boolean_t ignore_ancestry, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const char *url, *target, *source_root, *switch_rev_url; svn_ra_session_t *ra_session; svn_revnum_t revnum; svn_error_t *err = SVN_NO_ERROR; const char *diff3_cmd; svn_boolean_t use_commit_times; svn_boolean_t sleep_here = FALSE; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; struct svn_client__dirent_fetcher_baton_t dfb; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; /* Do not support the situation of both exclude and switch a target. */ if (depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); { svn_boolean_t has_working; SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath, pool)); if (has_working) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot switch '%s' because it is not in the " "repository yet"), svn_dirent_local_style(local_abspath, pool)); } /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, local_abspath, TRUE, pool)); if (target_kind == svn_node_dir) SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &switch_rev_url, switch_url, anchor_abspath, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_uri__is_ancestor(source_root, url)) return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\nis not the same repository as\n'%s'"), url, source_root); /* If we're not ignoring ancestry, then error out if the switch source and target don't have a common ancestory. ### We're acting on the anchor here, not the target. Is that ### okay? */ if (! ignore_ancestry) { const char *target_url, *yc_path; svn_revnum_t target_rev, yc_rev; SVN_ERR(svn_wc__node_get_url(&target_url, ctx->wc_ctx, local_abspath, pool, pool)); SVN_ERR(svn_wc__node_get_base_rev(&target_rev, ctx->wc_ctx, local_abspath, pool)); /* ### It would be nice if this function could reuse the existing ra session instead of opening two for its own use. */ SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_path, &yc_rev, switch_rev_url, revnum, target_url, target_rev, ctx, pool)); if (! (yc_path && SVN_IS_VALID_REVNUM(yc_rev))) return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, _("'%s' shares no common ancestry with '%s'"), switch_url, local_abspath); } SVN_ERR(svn_ra_reparent(ra_session, url, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; SVN_ERR(svn_ra_get_session_url(ra_session, &dfb.anchor_url, pool)); dfb.target_revision = revnum; SVN_ERR(svn_wc_get_switch_editor4(&switch_editor, &switch_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, switch_rev_url, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, server_supports_depth, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, ctx->conflict_func2, ctx->conflict_baton2, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch2(ra_session, &reporter, &report_baton, revnum, target, depth_is_sticky ? depth : svn_depth_unknown, switch_rev_url, switch_editor, switch_edit_baton, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. We pass in an external_func for recording all externals. It shouldn't be needed for a switch if it wasn't for the relative externals of type '../path'. All of those must be resolved to the new location. */ err = svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(local_abspath, pool); return svn_error_trace(err); } *use_sleep = TRUE; /* We handle externals after the switch is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, source_root, local_abspath, depth, use_sleep, ctx, pool)); } /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) svn_io_sleep_for_timestamps(local_abspath, pool); /* Return errors we might have sustained. */ if (err) return svn_error_trace(err); /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
/* Like svn_ra_stat() but with a compatibility hack for pre-1.2 svnserve. */ static svn_error_t * ra_stat_compatible(svn_ra_session_t *ra_session, svn_revnum_t rev, svn_dirent_t **dirent_p, apr_uint32_t dirent_fields, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *repos_root_URL, *url; svn_error_t *err; svn_dirent_t *the_ent; SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_URL, pool)); SVN_ERR(svn_ra_get_session_url(ra_session, &url, pool)); err = svn_ra_stat(ra_session, "", rev, &the_ent, pool); /* svn_ra_stat() will work against old versions of mod_dav_svn, but not old versions of svnserve. In the case of a pre-1.2 svnserve, catch the specific error it throws:*/ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { /* Fall back to pre-1.2 strategy for fetching dirent's URL. */ svn_node_kind_t url_kind; svn_ra_session_t *parent_ra_session; apr_hash_t *parent_ents; const char *parent_url, *base_name; if (strcmp(url, repos_root_URL) == 0) { /* In this universe, there's simply no way to fetch information about the repository's root directory! */ return err; } svn_error_clear(err); SVN_ERR(svn_ra_check_path(ra_session, "", rev, &url_kind, pool)); if (url_kind == svn_node_none) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Open a new RA session to the item's parent. */ svn_uri_split(&parent_url, &base_name, url, pool); SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session, NULL, parent_url, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Get all parent's entries, and find the item's dirent in the hash. */ SVN_ERR(svn_ra_get_dir2(parent_ra_session, &parent_ents, NULL, NULL, "", rev, dirent_fields, pool)); the_ent = apr_hash_get(parent_ents, base_name, APR_HASH_KEY_STRING); if (the_ent == NULL) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); } else if (err) { return svn_error_trace(err); } *dirent_p = the_ent; return SVN_NO_ERROR; }
svn_error_t * svn_ra__get_inherited_props_walk(svn_ra_session_t *session, const char *path, svn_revnum_t revision, apr_array_header_t **inherited_props, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *repos_root_url; const char *session_url; const char *parent_url; apr_pool_t *iterpool = svn_pool_create(scratch_pool); *inherited_props = apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *)); /* Walk to the root of the repository getting inherited props for PATH. */ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool)); SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool)); parent_url = session_url; while (strcmp(repos_root_url, parent_url)) { apr_hash_index_t *hi; apr_hash_t *parent_props; apr_hash_t *final_hash = apr_hash_make(result_pool); svn_error_t *err; svn_pool_clear(iterpool); parent_url = svn_uri_dirname(parent_url, scratch_pool); SVN_ERR(svn_ra_reparent(session, parent_url, iterpool)); err = session->vtable->get_dir(session, NULL, NULL, &parent_props, "", revision, SVN_DIRENT_ALL, iterpool); /* If the user doesn't have read access to a parent path then skip, but allow them to inherit from further up. */ if (err) { if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)) { svn_error_clear(err); continue; } else { return svn_error_trace(err); } } for (hi = apr_hash_first(scratch_pool, parent_props); hi; hi = apr_hash_next(hi)) { const char *name = apr_hash_this_key(hi); apr_ssize_t klen = apr_hash_this_key_len(hi); svn_string_t *value = apr_hash_this_val(hi); if (svn_property_kind2(name) == svn_prop_regular_kind) { name = apr_pstrdup(result_pool, name); value = svn_string_dup(value, result_pool); apr_hash_set(final_hash, name, klen, value); } } if (apr_hash_count(final_hash)) { svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool, sizeof(*new_iprop)); new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url, parent_url, result_pool); new_iprop->prop_hash = final_hash; svn_sort__array_insert(*inherited_props, &new_iprop, 0); } } /* Reparent session back to original URL. */ SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool)); svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
svn_error_t * svn_ra__file_revs_from_log(svn_ra_session_t *ra_session, const char *path, svn_revnum_t start, svn_revnum_t end, svn_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool) { svn_node_kind_t kind; const char *repos_url, *session_url, *fs_path; apr_array_header_t *condensed_targets; struct fr_log_message_baton lmb; struct rev *rev; apr_hash_t *last_props; svn_stream_t *last_stream; apr_pool_t *currpool, *lastpool; /* Fetch the absolute FS path associated with PATH. */ SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool)); /* Check to make sure we're dealing with a file. */ SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool)); if (kind == svn_node_dir) return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file"), fs_path); condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(condensed_targets, const char *) = path; lmb.path = fs_path; lmb.eldest = NULL; lmb.pool = pool; /* Accumulate revision metadata by walking the revisions backwards; this allows us to follow moves/copies correctly. */ SVN_ERR(svn_ra_get_log2(ra_session, condensed_targets, end, start, 0, /* no limit */ TRUE, FALSE, FALSE, NULL, fr_log_message_receiver, &lmb, pool)); /* Reparent the session while we go back through the history. */ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool)); SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool)); currpool = svn_pool_create(pool); lastpool = svn_pool_create(pool); /* We want the first txdelta to be against the empty file. */ last_props = apr_hash_make(lastpool); last_stream = svn_stream_empty(lastpool); /* Walk the revision list in chronological order, downloading each fulltext, diffing it with its predecessor, and calling the file_revs handler for each one. Use two iteration pools rather than one, because the diff routines need to look at a sliding window of revisions. Two pools gives us a ring buffer of sorts. */ for (rev = lmb.eldest; rev; rev = rev->next) { const char *temp_path; apr_pool_t *tmppool; apr_hash_t *props; apr_file_t *file; svn_stream_t *stream; apr_array_header_t *prop_diffs; svn_txdelta_stream_t *delta_stream; svn_txdelta_window_handler_t delta_handler = NULL; void *delta_baton = NULL; svn_pool_clear(currpool); /* Get the contents of the file from the repository, and put them in a temporary local file. */ SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL, svn_io_file_del_on_pool_cleanup, currpool, currpool)); SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, stream, NULL, &props, currpool)); SVN_ERR(svn_stream_close(stream)); /* Open up a stream to the local file. */ SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT, currpool)); stream = svn_stream_from_aprfile2(file, FALSE, currpool); /* Calculate the property diff */ SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool)); /* Call the file_rev handler */ SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props, FALSE, /* merged revision */ &delta_handler, &delta_baton, prop_diffs, lastpool)); /* Compute and send delta if client asked for it. */ if (delta_handler) { /* Get the content delta. Don't calculate checksums as we don't * use them. */ svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool); /* And send. */ SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, delta_baton, lastpool)); } /* Switch the pools and data for the next iteration */ tmppool = currpool; currpool = lastpool; lastpool = tmppool; SVN_ERR(svn_stream_close(last_stream)); last_stream = stream; last_props = props; } SVN_ERR(svn_stream_close(last_stream)); svn_pool_destroy(currpool); svn_pool_destroy(lastpool); /* Reparent the session back to the original URL. */ return svn_ra_reparent(ra_session, session_url, pool); }